summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt81
-rw-r--r--src/audio_core/cubeb_sink.cpp6
-rw-r--r--src/audio_core/cubeb_sink.h2
-rw-r--r--src/audio_core/stream.cpp16
-rw-r--r--src/audio_core/stream.h7
-rw-r--r--src/common/CMakeLists.txt18
-rw-r--r--src/common/assert.h18
-rw-r--r--src/common/bit_field.h28
-rw-r--r--src/common/bit_util.h47
-rw-r--r--src/common/common_types.h7
-rw-r--r--src/common/detached_tasks.cpp8
-rw-r--r--src/common/file_util.cpp49
-rw-r--r--src/common/file_util.h15
-rw-r--r--src/common/hex_util.cpp7
-rw-r--r--src/common/hex_util.h16
-rw-r--r--src/common/logging/backend.cpp6
-rw-r--r--src/common/lz4_compression.cpp76
-rw-r--r--src/common/lz4_compression.h57
-rw-r--r--src/common/math_util.h3
-rw-r--r--src/common/memory_hook.cpp (renamed from src/core/memory_hook.cpp)6
-rw-r--r--src/common/memory_hook.h (renamed from src/core/memory_hook.h)4
-rw-r--r--src/common/multi_level_queue.h337
-rw-r--r--src/common/page_table.cpp31
-rw-r--r--src/common/page_table.h84
-rw-r--r--src/common/scope_exit.h2
-rw-r--r--src/common/swap.h270
-rw-r--r--src/common/thread.cpp37
-rw-r--r--src/common/thread.h14
-rw-r--r--src/common/thread_queue_list.h6
-rw-r--r--src/common/threadsafe_queue.h4
-rw-r--r--src/common/uint128.cpp45
-rw-r--r--src/common/uint128.h19
-rw-r--r--src/common/uuid.cpp33
-rw-r--r--src/common/uuid.h48
-rw-r--r--src/common/zstd_compression.cpp51
-rw-r--r--src/common/zstd_compression.h44
-rw-r--r--src/core/CMakeLists.txt31
-rw-r--r--src/core/arm/arm_interface.h14
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic.cpp42
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic.h26
-rw-r--r--src/core/arm/unicorn/arm_unicorn.cpp42
-rw-r--r--src/core/arm/unicorn/arm_unicorn.h18
-rw-r--r--src/core/constants.cpp17
-rw-r--r--src/core/constants.h17
-rw-r--r--src/core/core.cpp117
-rw-r--r--src/core/core.h44
-rw-r--r--src/core/core_cpu.cpp21
-rw-r--r--src/core/core_cpu.h8
-rw-r--r--src/core/core_timing.cpp2
-rw-r--r--src/core/core_timing.h2
-rw-r--r--src/core/core_timing_util.cpp44
-rw-r--r--src/core/core_timing_util.h53
-rw-r--r--src/core/cpu_core_manager.cpp9
-rw-r--r--src/core/cpu_core_manager.h7
-rw-r--r--src/core/crypto/key_manager.cpp7
-rw-r--r--src/core/file_sys/card_image.cpp46
-rw-r--r--src/core/file_sys/cheat_engine.cpp492
-rw-r--r--src/core/file_sys/cheat_engine.h234
-rw-r--r--src/core/file_sys/content_archive.h15
-rw-r--r--src/core/file_sys/control_metadata.cpp14
-rw-r--r--src/core/file_sys/control_metadata.h35
-rw-r--r--src/core/file_sys/errors.h3
-rw-r--r--src/core/file_sys/fsmitm_romfsbuild.cpp4
-rw-r--r--src/core/file_sys/ips_layer.cpp3
-rw-r--r--src/core/file_sys/nca_metadata.cpp8
-rw-r--r--src/core/file_sys/nca_metadata.h12
-rw-r--r--src/core/file_sys/patch_manager.cpp107
-rw-r--r--src/core/file_sys/patch_manager.h11
-rw-r--r--src/core/file_sys/program_metadata.cpp27
-rw-r--r--src/core/file_sys/program_metadata.h2
-rw-r--r--src/core/file_sys/registered_cache.cpp291
-rw-r--r--src/core/file_sys/registered_cache.h156
-rw-r--r--src/core/file_sys/romfs_factory.cpp2
-rw-r--r--src/core/file_sys/savedata_factory.cpp8
-rw-r--r--src/core/file_sys/savedata_factory.h11
-rw-r--r--src/core/file_sys/submission_package.cpp26
-rw-r--r--src/core/file_sys/submission_package.h11
-rw-r--r--src/core/file_sys/system_archive/system_archive.cpp3
-rw-r--r--src/core/file_sys/system_archive/system_version.cpp52
-rw-r--r--src/core/file_sys/system_archive/system_version.h16
-rw-r--r--src/core/file_sys/xts_archive.cpp2
-rw-r--r--src/core/frontend/applets/error.cpp34
-rw-r--r--src/core/frontend/applets/error.h37
-rw-r--r--src/core/frontend/applets/general_frontend.cpp27
-rw-r--r--src/core/frontend/applets/general_frontend.h28
-rw-r--r--src/core/frontend/applets/profile_select.cpp5
-rw-r--r--src/core/frontend/applets/profile_select.h8
-rw-r--r--src/core/frontend/emu_window.cpp8
-rw-r--r--src/core/frontend/emu_window.h44
-rw-r--r--src/core/frontend/framebuffer_layout.cpp13
-rw-r--r--src/core/frontend/framebuffer_layout.h21
-rw-r--r--src/core/gdbstub/gdbstub.cpp2
-rw-r--r--src/core/hle/ipc.h44
-rw-r--r--src/core/hle/ipc_helpers.h54
-rw-r--r--src/core/hle/kernel/address_arbiter.cpp44
-rw-r--r--src/core/hle/kernel/address_arbiter.h16
-rw-r--r--src/core/hle/kernel/client_port.cpp15
-rw-r--r--src/core/hle/kernel/client_port.h2
-rw-r--r--src/core/hle/kernel/client_session.cpp14
-rw-r--r--src/core/hle/kernel/client_session.h11
-rw-r--r--src/core/hle/kernel/code_set.cpp12
-rw-r--r--src/core/hle/kernel/code_set.h89
-rw-r--r--src/core/hle/kernel/hle_ipc.cpp31
-rw-r--r--src/core/hle/kernel/hle_ipc.h23
-rw-r--r--src/core/hle/kernel/kernel.cpp31
-rw-r--r--src/core/hle/kernel/kernel.h10
-rw-r--r--src/core/hle/kernel/mutex.cpp35
-rw-r--r--src/core/hle/kernel/mutex.h20
-rw-r--r--src/core/hle/kernel/object.cpp2
-rw-r--r--src/core/hle/kernel/object.h6
-rw-r--r--src/core/hle/kernel/process.cpp104
-rw-r--r--src/core/hle/kernel/process.h140
-rw-r--r--src/core/hle/kernel/readable_event.cpp5
-rw-r--r--src/core/hle/kernel/readable_event.h4
-rw-r--r--src/core/hle/kernel/resource_limit.cpp7
-rw-r--r--src/core/hle/kernel/resource_limit.h13
-rw-r--r--src/core/hle/kernel/scheduler.cpp79
-rw-r--r--src/core/hle/kernel/scheduler.h12
-rw-r--r--src/core/hle/kernel/server_port.cpp13
-rw-r--r--src/core/hle/kernel/server_port.h44
-rw-r--r--src/core/hle/kernel/server_session.cpp100
-rw-r--r--src/core/hle/kernel/server_session.h64
-rw-r--r--src/core/hle/kernel/shared_memory.cpp16
-rw-r--r--src/core/hle/kernel/shared_memory.h12
-rw-r--r--src/core/hle/kernel/svc.cpp903
-rw-r--r--src/core/hle/kernel/svc.h6
-rw-r--r--src/core/hle/kernel/svc_wrap.h357
-rw-r--r--src/core/hle/kernel/thread.cpp111
-rw-r--r--src/core/hle/kernel/thread.h113
-rw-r--r--src/core/hle/kernel/transfer_memory.cpp81
-rw-r--r--src/core/hle/kernel/transfer_memory.h103
-rw-r--r--src/core/hle/kernel/vm_manager.cpp179
-rw-r--r--src/core/hle/kernel/vm_manager.h128
-rw-r--r--src/core/hle/kernel/wait_object.cpp31
-rw-r--r--src/core/hle/kernel/wait_object.h4
-rw-r--r--src/core/hle/kernel/writable_event.h2
-rw-r--r--src/core/hle/result.h22
-rw-r--r--src/core/hle/service/acc/acc.cpp93
-rw-r--r--src/core/hle/service/acc/acc.h9
-rw-r--r--src/core/hle/service/acc/acc_aa.cpp5
-rw-r--r--src/core/hle/service/acc/acc_aa.h4
-rw-r--r--src/core/hle/service/acc/acc_su.cpp11
-rw-r--r--src/core/hle/service/acc/acc_su.h4
-rw-r--r--src/core/hle/service/acc/acc_u0.cpp15
-rw-r--r--src/core/hle/service/acc/acc_u0.h4
-rw-r--r--src/core/hle/service/acc/acc_u1.cpp11
-rw-r--r--src/core/hle/service/acc/acc_u1.h4
-rw-r--r--src/core/hle/service/acc/profile_manager.cpp28
-rw-r--r--src/core/hle/service/acc/profile_manager.h66
-rw-r--r--src/core/hle/service/am/am.cpp294
-rw-r--r--src/core/hle/service/am/am.h34
-rw-r--r--src/core/hle/service/am/applet_ae.cpp8
-rw-r--r--src/core/hle/service/am/applets/applets.cpp103
-rw-r--r--src/core/hle/service/am/applets/applets.h72
-rw-r--r--src/core/hle/service/am/applets/error.cpp182
-rw-r--r--src/core/hle/service/am/applets/error.h47
-rw-r--r--src/core/hle/service/am/applets/general_backend.cpp (renamed from src/core/hle/service/am/applets/stub_applet.cpp)68
-rw-r--r--src/core/hle/service/am/applets/general_backend.h48
-rw-r--r--src/core/hle/service/am/applets/profile_select.cpp14
-rw-r--r--src/core/hle/service/am/applets/profile_select.h9
-rw-r--r--src/core/hle/service/am/applets/software_keyboard.cpp7
-rw-r--r--src/core/hle/service/am/applets/software_keyboard.h4
-rw-r--r--src/core/hle/service/am/applets/stub_applet.h24
-rw-r--r--src/core/hle/service/am/applets/web_browser.cpp6
-rw-r--r--src/core/hle/service/am/applets/web_browser.h4
-rw-r--r--src/core/hle/service/aoc/aoc_u.cpp79
-rw-r--r--src/core/hle/service/apm/interface.cpp2
-rw-r--r--src/core/hle/service/audio/audctl.cpp30
-rw-r--r--src/core/hle/service/audio/audctl.h4
-rw-r--r--src/core/hle/service/audio/audin_u.cpp5
-rw-r--r--src/core/hle/service/audio/audout_u.cpp34
-rw-r--r--src/core/hle/service/audio/audrec_u.cpp4
-rw-r--r--src/core/hle/service/audio/audren_u.cpp365
-rw-r--r--src/core/hle/service/audio/audren_u.h2
-rw-r--r--src/core/hle/service/audio/hwopus.cpp206
-rw-r--r--src/core/hle/service/btdrv/btdrv.cpp7
-rw-r--r--src/core/hle/service/btm/btm.cpp8
-rw-r--r--src/core/hle/service/caps/caps.cpp89
-rw-r--r--src/core/hle/service/fatal/fatal.cpp89
-rw-r--r--src/core/hle/service/filesystem/filesystem.cpp30
-rw-r--r--src/core/hle/service/filesystem/filesystem.h4
-rw-r--r--src/core/hle/service/filesystem/fsp_srv.cpp116
-rw-r--r--src/core/hle/service/filesystem/fsp_srv.h1
-rw-r--r--src/core/hle/service/friend/friend.cpp5
-rw-r--r--src/core/hle/service/hid/controllers/debug_pad.h30
-rw-r--r--src/core/hle/service/hid/controllers/npad.cpp2
-rw-r--r--src/core/hle/service/hid/controllers/npad.h102
-rw-r--r--src/core/hle/service/hid/controllers/touchscreen.h4
-rw-r--r--src/core/hle/service/hid/hid.cpp25
-rw-r--r--src/core/hle/service/hid/hid.h5
-rw-r--r--src/core/hle/service/ldn/ldn.cpp54
-rw-r--r--src/core/hle/service/ldr/ldr.cpp157
-rw-r--r--src/core/hle/service/lm/lm.cpp2
-rw-r--r--src/core/hle/service/mii/mii.cpp341
-rw-r--r--src/core/hle/service/mii/mii_manager.cpp416
-rw-r--r--src/core/hle/service/mii/mii_manager.h273
-rw-r--r--src/core/hle/service/ncm/ncm.cpp80
-rw-r--r--src/core/hle/service/nfc/nfc.cpp2
-rw-r--r--src/core/hle/service/nfp/nfp.cpp8
-rw-r--r--src/core/hle/service/nifm/nifm.cpp8
-rw-r--r--src/core/hle/service/nim/nim.cpp2
-rw-r--r--src/core/hle/service/npns/npns.cpp3
-rw-r--r--src/core/hle/service/ns/errors.h12
-rw-r--r--src/core/hle/service/ns/language.cpp392
-rw-r--r--src/core/hle/service/ns/language.h45
-rw-r--r--src/core/hle/service/ns/ns.cpp862
-rw-r--r--src/core/hle/service/ns/ns.h82
-rw-r--r--src/core/hle/service/ns/ns_language.h42
-rw-r--r--src/core/hle/service/nvdrv/devices/nvdevice.h10
-rw-r--r--src/core/hle/service/nvdrv/devices/nvdisp_disp0.h2
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp17
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp3
-rw-r--r--src/core/hle/service/nvdrv/interface.cpp2
-rw-r--r--src/core/hle/service/nvdrv/interface.h2
-rw-r--r--src/core/hle/service/nvdrv/nvmemp.h2
-rw-r--r--src/core/hle/service/nvflinger/buffer_queue.cpp2
-rw-r--r--src/core/hle/service/nvflinger/nvflinger.cpp16
-rw-r--r--src/core/hle/service/pctl/module.cpp7
-rw-r--r--src/core/hle/service/pm/pm.cpp21
-rw-r--r--src/core/hle/service/service.cpp12
-rw-r--r--src/core/hle/service/service.h2
-rw-r--r--src/core/hle/service/set/set.cpp60
-rw-r--r--src/core/hle/service/set/set_cal.cpp2
-rw-r--r--src/core/hle/service/set/set_cal.h2
-rw-r--r--src/core/hle/service/set/set_sys.cpp106
-rw-r--r--src/core/hle/service/set/set_sys.h2
-rw-r--r--src/core/hle/service/sm/controller.cpp2
-rw-r--r--src/core/hle/service/sm/sm.h2
-rw-r--r--src/core/hle/service/sockets/bsd.cpp5
-rw-r--r--src/core/hle/service/sockets/sfdnsres.cpp12
-rw-r--r--src/core/hle/service/spl/module.cpp4
-rw-r--r--src/core/hle/service/ssl/ssl.cpp12
-rw-r--r--src/core/hle/service/time/interface.cpp10
-rw-r--r--src/core/hle/service/time/time.cpp9
-rw-r--r--src/core/hle/service/vi/display/vi_display.cpp2
-rw-r--r--src/core/hle/service/vi/vi.cpp45
-rw-r--r--src/core/hle/service/vi/vi.h40
-rw-r--r--src/core/hle/service/vi/vi_m.cpp12
-rw-r--r--src/core/hle/service/vi/vi_m.h19
-rw-r--r--src/core/hle/service/vi/vi_s.cpp12
-rw-r--r--src/core/hle/service/vi/vi_s.h19
-rw-r--r--src/core/hle/service/vi/vi_u.cpp12
-rw-r--r--src/core/hle/service/vi/vi_u.h19
-rw-r--r--src/core/loader/deconstructed_rom_directory.cpp40
-rw-r--r--src/core/loader/deconstructed_rom_directory.h2
-rw-r--r--src/core/loader/elf.cpp18
-rw-r--r--src/core/loader/elf.h2
-rw-r--r--src/core/loader/linker.cpp147
-rw-r--r--src/core/loader/linker.h36
-rw-r--r--src/core/loader/loader.h19
-rw-r--r--src/core/loader/nax.cpp30
-rw-r--r--src/core/loader/nax.h2
-rw-r--r--src/core/loader/nca.cpp26
-rw-r--r--src/core/loader/nca.h2
-rw-r--r--src/core/loader/nro.cpp17
-rw-r--r--src/core/loader/nro.h6
-rw-r--r--src/core/loader/nso.cpp130
-rw-r--r--src/core/loader/nso.h45
-rw-r--r--src/core/loader/nsp.cpp38
-rw-r--r--src/core/loader/nsp.h2
-rw-r--r--src/core/loader/xci.cpp28
-rw-r--r--src/core/loader/xci.h4
-rw-r--r--src/core/memory.cpp234
-rw-r--r--src/core/memory.h104
-rw-r--r--src/core/memory_setup.h19
-rw-r--r--src/core/perf_stats.cpp10
-rw-r--r--src/core/settings.cpp4
-rw-r--r--src/core/settings.h2
-rw-r--r--src/core/telemetry_session.cpp54
-rw-r--r--src/core/telemetry_session.h32
-rw-r--r--src/input_common/CMakeLists.txt15
-rw-r--r--src/input_common/keyboard.cpp8
-rw-r--r--src/input_common/main.cpp23
-rw-r--r--src/input_common/main.h2
-rw-r--r--src/input_common/motion_emu.cpp10
-rw-r--r--src/input_common/sdl/sdl.cpp636
-rw-r--r--src/input_common/sdl/sdl.h56
-rw-r--r--src/input_common/sdl/sdl_impl.cpp667
-rw-r--r--src/input_common/sdl/sdl_impl.h72
-rw-r--r--src/tests/CMakeLists.txt3
-rw-r--r--src/tests/common/bit_field.cpp90
-rw-r--r--src/tests/common/bit_utils.cpp23
-rw-r--r--src/tests/common/multi_level_queue.cpp55
-rw-r--r--src/tests/core/arm/arm_test_common.cpp8
-rw-r--r--src/tests/core/arm/arm_test_common.h8
-rw-r--r--src/video_core/CMakeLists.txt34
-rw-r--r--src/video_core/debug_utils/debug_utils.cpp4
-rw-r--r--src/video_core/debug_utils/debug_utils.h4
-rw-r--r--src/video_core/dma_pusher.cpp19
-rw-r--r--src/video_core/dma_pusher.h1
-rw-r--r--src/video_core/engines/const_buffer_info.h17
-rw-r--r--src/video_core/engines/engine_upload.cpp52
-rw-r--r--src/video_core/engines/engine_upload.h73
-rw-r--r--src/video_core/engines/fermi_2d.cpp3
-rw-r--r--src/video_core/engines/fermi_2d.h14
-rw-r--r--src/video_core/engines/kepler_compute.cpp37
-rw-r--r--src/video_core/engines/kepler_compute.h184
-rw-r--r--src/video_core/engines/kepler_memory.cpp37
-rw-r--r--src/video_core/engines/kepler_memory.h49
-rw-r--r--src/video_core/engines/maxwell_3d.cpp152
-rw-r--r--src/video_core/engines/maxwell_3d.h69
-rw-r--r--src/video_core/engines/maxwell_dma.cpp90
-rw-r--r--src/video_core/engines/maxwell_dma.h52
-rw-r--r--src/video_core/engines/shader_bytecode.h155
-rw-r--r--src/video_core/gpu.cpp21
-rw-r--r--src/video_core/gpu.h30
-rw-r--r--src/video_core/gpu_asynch.cpp12
-rw-r--r--src/video_core/gpu_asynch.h13
-rw-r--r--src/video_core/gpu_synch.cpp10
-rw-r--r--src/video_core/gpu_synch.h9
-rw-r--r--src/video_core/gpu_thread.cpp156
-rw-r--r--src/video_core/gpu_thread.h135
-rw-r--r--src/video_core/macro_interpreter.cpp26
-rw-r--r--src/video_core/memory_manager.cpp615
-rw-r--r--src/video_core/memory_manager.h195
-rw-r--r--src/video_core/morton.cpp324
-rw-r--r--src/video_core/morton.h6
-rw-r--r--src/video_core/rasterizer_cache.h82
-rw-r--r--src/video_core/rasterizer_interface.h9
-rw-r--r--src/video_core/renderer_opengl/gl_buffer_cache.cpp42
-rw-r--r--src/video_core/renderer_opengl/gl_buffer_cache.h40
-rw-r--r--src/video_core/renderer_opengl/gl_device.cpp109
-rw-r--r--src/video_core/renderer_opengl/gl_device.h48
-rw-r--r--src/video_core/renderer_opengl/gl_global_cache.cpp69
-rw-r--r--src/video_core/renderer_opengl/gl_global_cache.h36
-rw-r--r--src/video_core/renderer_opengl/gl_primitive_assembler.cpp67
-rw-r--r--src/video_core/renderer_opengl/gl_primitive_assembler.h33
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp446
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.h81
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer_cache.cpp214
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer_cache.h105
-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.cpp282
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.h48
-rw-r--r--src/video_core/renderer_opengl/gl_shader_decompiler.cpp1068
-rw-r--r--src/video_core/renderer_opengl/gl_shader_decompiler.h27
-rw-r--r--src/video_core/renderer_opengl/gl_shader_disk_cache.cpp297
-rw-r--r--src/video_core/renderer_opengl/gl_shader_disk_cache.h83
-rw-r--r--src/video_core/renderer_opengl/gl_shader_gen.cpp86
-rw-r--r--src/video_core/renderer_opengl/gl_shader_gen.h32
-rw-r--r--src/video_core/renderer_opengl/gl_shader_manager.cpp58
-rw-r--r--src/video_core/renderer_opengl/gl_shader_manager.h81
-rw-r--r--src/video_core/renderer_opengl/gl_state.cpp625
-rw-r--r--src/video_core/renderer_opengl/gl_state.h59
-rw-r--r--src/video_core/renderer_opengl/maxwell_to_gl.h64
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.cpp18
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.h3
-rw-r--r--src/video_core/renderer_opengl/utils.cpp44
-rw-r--r--src/video_core/renderer_opengl/utils.h24
-rw-r--r--src/video_core/renderer_vulkan/maxwell_to_vk.cpp485
-rw-r--r--src/video_core/renderer_vulkan/maxwell_to_vk.h58
-rw-r--r--src/video_core/renderer_vulkan/vk_buffer_cache.cpp31
-rw-r--r--src/video_core/renderer_vulkan/vk_buffer_cache.h39
-rw-r--r--src/video_core/renderer_vulkan/vk_device.cpp131
-rw-r--r--src/video_core/renderer_vulkan/vk_device.h58
-rw-r--r--src/video_core/renderer_vulkan/vk_resource_manager.cpp2
-rw-r--r--src/video_core/renderer_vulkan/vk_resource_manager.h2
-rw-r--r--src/video_core/renderer_vulkan/vk_sampler_cache.cpp65
-rw-r--r--src/video_core/renderer_vulkan/vk_sampler_cache.h32
-rw-r--r--src/video_core/renderer_vulkan/vk_shader_decompiler.cpp1455
-rw-r--r--src/video_core/renderer_vulkan/vk_shader_decompiler.h84
-rw-r--r--src/video_core/renderer_vulkan/vk_swapchain.cpp210
-rw-r--r--src/video_core/renderer_vulkan/vk_swapchain.h92
-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/decode.cpp6
-rw-r--r--src/video_core/shader/decode/arithmetic.cpp4
-rw-r--r--src/video_core/shader/decode/arithmetic_half.cpp33
-rw-r--r--src/video_core/shader/decode/arithmetic_half_immediate.cpp21
-rw-r--r--src/video_core/shader/decode/arithmetic_immediate.cpp3
-rw-r--r--src/video_core/shader/decode/arithmetic_integer.cpp1
-rw-r--r--src/video_core/shader/decode/arithmetic_integer_immediate.cpp3
-rw-r--r--src/video_core/shader/decode/bfe.cpp3
-rw-r--r--src/video_core/shader/decode/bfi.cpp3
-rw-r--r--src/video_core/shader/decode/conversion.cpp77
-rw-r--r--src/video_core/shader/decode/ffma.cpp3
-rw-r--r--src/video_core/shader/decode/float_set.cpp3
-rw-r--r--src/video_core/shader/decode/float_set_predicate.cpp3
-rw-r--r--src/video_core/shader/decode/half_set.cpp20
-rw-r--r--src/video_core/shader/decode/half_set_predicate.cpp11
-rw-r--r--src/video_core/shader/decode/hfma2.cpp22
-rw-r--r--src/video_core/shader/decode/integer_set.cpp4
-rw-r--r--src/video_core/shader/decode/integer_set_predicate.cpp3
-rw-r--r--src/video_core/shader/decode/memory.cpp606
-rw-r--r--src/video_core/shader/decode/other.cpp75
-rw-r--r--src/video_core/shader/decode/predicate_set_predicate.cpp3
-rw-r--r--src/video_core/shader/decode/predicate_set_register.cpp3
-rw-r--r--src/video_core/shader/decode/register_set_predicate.cpp3
-rw-r--r--src/video_core/shader/decode/shift.cpp3
-rw-r--r--src/video_core/shader/decode/texture.cpp672
-rw-r--r--src/video_core/shader/decode/video.cpp3
-rw-r--r--src/video_core/shader/decode/xmad.cpp43
-rw-r--r--src/video_core/shader/node.h519
-rw-r--r--src/video_core/shader/node_helper.cpp99
-rw-r--r--src/video_core/shader/node_helper.h65
-rw-r--r--src/video_core/shader/shader_ir.cpp162
-rw-r--r--src/video_core/shader/shader_ir.h584
-rw-r--r--src/video_core/shader/track.cpp43
-rw-r--r--src/video_core/surface.cpp88
-rw-r--r--src/video_core/texture_cache.cpp386
-rw-r--r--src/video_core/texture_cache.h586
-rw-r--r--src/video_core/textures/astc.cpp12
-rw-r--r--src/video_core/textures/convert.cpp1
-rw-r--r--src/video_core/textures/convert.h5
-rw-r--r--src/video_core/textures/decoders.cpp55
-rw-r--r--src/video_core/textures/decoders.h17
-rw-r--r--src/video_core/textures/texture.h87
-rw-r--r--src/video_core/video_core.cpp10
-rw-r--r--src/video_core/video_core.h7
-rw-r--r--src/web_service/web_backend.cpp4
-rw-r--r--src/yuzu/CMakeLists.txt31
-rw-r--r--src/yuzu/about_dialog.cpp8
-rw-r--r--src/yuzu/applets/error.cpp61
-rw-r--r--src/yuzu/applets/error.h33
-rw-r--r--src/yuzu/applets/profile_select.cpp45
-rw-r--r--src/yuzu/applets/profile_select.h18
-rw-r--r--src/yuzu/applets/software_keyboard.cpp47
-rw-r--r--src/yuzu/applets/software_keyboard.h3
-rw-r--r--src/yuzu/applets/web_browser.cpp6
-rw-r--r--src/yuzu/bootmanager.cpp278
-rw-r--r--src/yuzu/bootmanager.h44
-rw-r--r--src/yuzu/compatdb.cpp6
-rw-r--r--src/yuzu/configuration/config.cpp926
-rw-r--r--src/yuzu/configuration/config.h34
-rw-r--r--src/yuzu/configuration/configure.ui19
-rw-r--r--src/yuzu/configuration/configure_audio.cpp67
-rw-r--r--src/yuzu/configuration/configure_audio.h19
-rw-r--r--src/yuzu/configuration/configure_audio.ui6
-rw-r--r--src/yuzu/configuration/configure_debug.cpp19
-rw-r--r--src/yuzu/configuration/configure_debug.h7
-rw-r--r--src/yuzu/configuration/configure_debug.ui18
-rw-r--r--src/yuzu/configuration/configure_dialog.cpp99
-rw-r--r--src/yuzu/configuration/configure_dialog.h11
-rw-r--r--src/yuzu/configuration/configure_gamelist.cpp23
-rw-r--r--src/yuzu/configuration/configure_gamelist.h4
-rw-r--r--src/yuzu/configuration/configure_general.cpp31
-rw-r--r--src/yuzu/configuration/configure_general.h8
-rw-r--r--src/yuzu/configuration/configure_general.ui44
-rw-r--r--src/yuzu/configuration/configure_graphics.cpp36
-rw-r--r--src/yuzu/configuration/configure_graphics.h7
-rw-r--r--src/yuzu/configuration/configure_graphics.ui7
-rw-r--r--src/yuzu/configuration/configure_hotkeys.cpp129
-rw-r--r--src/yuzu/configuration/configure_hotkeys.h43
-rw-r--r--src/yuzu/configuration/configure_hotkeys.ui42
-rw-r--r--src/yuzu/configuration/configure_input.cpp91
-rw-r--r--src/yuzu/configuration/configure_input.h14
-rw-r--r--src/yuzu/configuration/configure_input.ui6
-rw-r--r--src/yuzu/configuration/configure_input_player.cpp211
-rw-r--r--src/yuzu/configuration/configure_input_player.h15
-rw-r--r--src/yuzu/configuration/configure_input_simple.cpp30
-rw-r--r--src/yuzu/configuration/configure_input_simple.h7
-rw-r--r--src/yuzu/configuration/configure_mouse_advanced.cpp126
-rw-r--r--src/yuzu/configuration/configure_mouse_advanced.h15
-rw-r--r--src/yuzu/configuration/configure_per_general.cpp46
-rw-r--r--src/yuzu/configuration/configure_per_general.h11
-rw-r--r--src/yuzu/configuration/configure_profile_manager.cpp41
-rw-r--r--src/yuzu/configuration/configure_profile_manager.h8
-rw-r--r--src/yuzu/configuration/configure_system.cpp91
-rw-r--r--src/yuzu/configuration/configure_system.h11
-rw-r--r--src/yuzu/configuration/configure_system.ui166
-rw-r--r--src/yuzu/configuration/configure_touchscreen_advanced.cpp22
-rw-r--r--src/yuzu/configuration/configure_touchscreen_advanced.h9
-rw-r--r--src/yuzu/configuration/configure_web.cpp66
-rw-r--r--src/yuzu/configuration/configure_web.h8
-rw-r--r--src/yuzu/debugger/graphics/graphics_breakpoints.cpp2
-rw-r--r--src/yuzu/debugger/graphics/graphics_surface.cpp461
-rw-r--r--src/yuzu/debugger/graphics/graphics_surface.h96
-rw-r--r--src/yuzu/debugger/profiler.cpp5
-rw-r--r--src/yuzu/debugger/profiler.h9
-rw-r--r--src/yuzu/debugger/wait_tree.cpp54
-rw-r--r--src/yuzu/game_list.cpp121
-rw-r--r--src/yuzu/game_list.h10
-rw-r--r--src/yuzu/game_list_p.h25
-rw-r--r--src/yuzu/game_list_worker.cpp243
-rw-r--r--src/yuzu/game_list_worker.h16
-rw-r--r--src/yuzu/hotkeys.cpp73
-rw-r--r--src/yuzu/hotkeys.h40
-rw-r--r--src/yuzu/hotkeys.ui46
-rw-r--r--src/yuzu/loading_screen.cpp25
-rw-r--r--src/yuzu/main.cpp512
-rw-r--r--src/yuzu/main.h21
-rw-r--r--src/yuzu/ui_settings.cpp1
-rw-r--r--src/yuzu/ui_settings.h12
-rw-r--r--src/yuzu/util/sequence_dialog/sequence_dialog.cpp40
-rw-r--r--src/yuzu/util/sequence_dialog/sequence_dialog.h24
-rw-r--r--src/yuzu/util/spinbox.cpp278
-rw-r--r--src/yuzu/util/spinbox.h86
-rw-r--r--src/yuzu/util/util.cpp18
-rw-r--r--src/yuzu_cmd/CMakeLists.txt2
-rw-r--r--src/yuzu_cmd/config.cpp16
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2.cpp144
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2.h24
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp150
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h34
-rw-r--r--src/yuzu_cmd/yuzu.cpp25
496 files changed, 23905 insertions, 11420 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index f69d00a2b..04018233f 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1,18 +1,99 @@
# Enable modules to include each other's files
include_directories(.)
+# CMake seems to only define _DEBUG on Windows
+set_property(DIRECTORY APPEND PROPERTY
+ COMPILE_DEFINITIONS $<$<CONFIG:Debug>:_DEBUG> $<$<NOT:$<CONFIG:Debug>>:NDEBUG>)
+
+# Set compilation flags
+if (MSVC)
+ set(CMAKE_CONFIGURATION_TYPES Debug Release CACHE STRING "" FORCE)
+
+ # Silence "deprecation" warnings
+ add_definitions(-D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE -D_SCL_SECURE_NO_WARNINGS)
+
+ # Avoid windows.h junk
+ add_definitions(-DNOMINMAX)
+
+ # Avoid windows.h from including some usually unused libs like winsocks.h, since this might cause some redefinition errors.
+ add_definitions(-DWIN32_LEAN_AND_MEAN)
+
+ # Ensure that projects build with Unicode support.
+ add_definitions(-DUNICODE -D_UNICODE)
+
+ # /W3 - Level 3 warnings
+ # /MP - Multi-threaded compilation
+ # /Zi - Output debugging information
+ # /Zo - Enhanced debug info for optimized builds
+ # /permissive- - Enables stricter C++ standards conformance checks
+ # /EHsc - C++-only exception handling semantics
+ # /volatile:iso - Use strict standards-compliant volatile semantics.
+ # /Zc:externConstexpr - Allow extern constexpr variables to have external linkage, like the standard mandates
+ # /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
+ /permissive-
+ /EHsc
+ /std:c++latest
+ /volatile:iso
+ /Zc:externConstexpr
+ /Zc:inline
+ /Zc:throwingNew
+ )
+
+ # /GS- - No stack buffer overflow checks
+ add_compile_options("$<$<CONFIG:Release>:/GS->")
+
+ set(CMAKE_EXE_LINKER_FLAGS_DEBUG "/DEBUG /MANIFEST:NO" CACHE STRING "" FORCE)
+ set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/DEBUG /MANIFEST:NO /INCREMENTAL:NO /OPT:REF,ICF" CACHE STRING "" FORCE)
+else()
+ add_compile_options(
+ -Wall
+ -Wno-attributes
+ )
+
+ if (APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL Clang)
+ add_compile_options("-stdlib=libc++")
+ endif()
+
+ # Set file offset size to 64 bits.
+ #
+ # On modern Unixes, this is typically already the case. The lone exception is
+ # glibc, which may default to 32 bits. glibc allows this to be configured
+ # by setting _FILE_OFFSET_BITS.
+ if(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR MINGW)
+ add_definitions(-D_FILE_OFFSET_BITS=64)
+ endif()
+
+ if (MINGW)
+ add_definitions(-DMINGW_HAS_SECURE_API)
+
+ if (MINGW_STATIC_BUILD)
+ add_definitions(-DQT_STATICPLUGIN)
+ add_compile_options("-static")
+ endif()
+ endif()
+endif()
+
add_subdirectory(common)
add_subdirectory(core)
add_subdirectory(audio_core)
add_subdirectory(video_core)
add_subdirectory(input_common)
add_subdirectory(tests)
+
if (ENABLE_SDL2)
add_subdirectory(yuzu_cmd)
endif()
+
if (ENABLE_QT)
add_subdirectory(yuzu)
endif()
+
if (ENABLE_WEB_SERVICE)
add_subdirectory(web_service)
endif()
diff --git a/src/audio_core/cubeb_sink.cpp b/src/audio_core/cubeb_sink.cpp
index 1da0b9f2a..7047ed9cf 100644
--- a/src/audio_core/cubeb_sink.cpp
+++ b/src/audio_core/cubeb_sink.cpp
@@ -12,7 +12,7 @@
#include "common/ring_buffer.h"
#include "core/settings.h"
-#ifdef _MSC_VER
+#ifdef _WIN32
#include <objbase.h>
#endif
@@ -113,7 +113,7 @@ private:
CubebSink::CubebSink(std::string_view target_device_name) {
// Cubeb requires COM to be initialized on the thread calling cubeb_init on Windows
-#ifdef _MSC_VER
+#ifdef _WIN32
com_init_result = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
#endif
@@ -152,7 +152,7 @@ CubebSink::~CubebSink() {
cubeb_destroy(ctx);
-#ifdef _MSC_VER
+#ifdef _WIN32
if (SUCCEEDED(com_init_result)) {
CoUninitialize();
}
diff --git a/src/audio_core/cubeb_sink.h b/src/audio_core/cubeb_sink.h
index 511df7bb1..7ce850f47 100644
--- a/src/audio_core/cubeb_sink.h
+++ b/src/audio_core/cubeb_sink.h
@@ -26,7 +26,7 @@ private:
cubeb_devid output_device{};
std::vector<SinkStreamPtr> sink_streams;
-#ifdef _MSC_VER
+#ifdef _WIN32
u32 com_init_result = 0;
#endif
};
diff --git a/src/audio_core/stream.cpp b/src/audio_core/stream.cpp
index 4b66a6786..982c7af2f 100644
--- a/src/audio_core/stream.cpp
+++ b/src/audio_core/stream.cpp
@@ -38,7 +38,7 @@ Stream::Stream(Core::Timing::CoreTiming& core_timing, u32 sample_rate, Format fo
sink_stream{sink_stream}, core_timing{core_timing}, name{std::move(name_)} {
release_event = core_timing.RegisterEvent(
- name, [this](u64 userdata, int cycles_late) { ReleaseActiveBuffer(); });
+ name, [this](u64 userdata, s64 cycles_late) { ReleaseActiveBuffer(); });
}
void Stream::Play() {
@@ -51,17 +51,23 @@ void Stream::Stop() {
UNIMPLEMENTED();
}
+void Stream::SetVolume(float volume) {
+ game_volume = volume;
+}
+
Stream::State Stream::GetState() const {
return state;
}
s64 Stream::GetBufferReleaseCycles(const Buffer& buffer) const {
const std::size_t num_samples{buffer.GetSamples().size() / GetNumChannels()};
- return Core::Timing::usToCycles((static_cast<u64>(num_samples) * 1000000) / sample_rate);
+ const auto us =
+ std::chrono::microseconds((static_cast<u64>(num_samples) * 1000000) / sample_rate);
+ return Core::Timing::usToCycles(us);
}
-static void VolumeAdjustSamples(std::vector<s16>& samples) {
- const float volume{std::clamp(Settings::values.volume, 0.0f, 1.0f)};
+static void VolumeAdjustSamples(std::vector<s16>& samples, float game_volume) {
+ const float volume{std::clamp(Settings::values.volume - (1.0f - game_volume), 0.0f, 1.0f)};
if (volume == 1.0f) {
return;
@@ -95,7 +101,7 @@ void Stream::PlayNextBuffer() {
active_buffer = queued_buffers.front();
queued_buffers.pop();
- VolumeAdjustSamples(active_buffer->GetSamples());
+ VolumeAdjustSamples(active_buffer->GetSamples(), game_volume);
sink_stream.EnqueueSamples(GetNumChannels(), active_buffer->GetSamples());
diff --git a/src/audio_core/stream.h b/src/audio_core/stream.h
index 05071243b..8106cea43 100644
--- a/src/audio_core/stream.h
+++ b/src/audio_core/stream.h
@@ -61,6 +61,12 @@ public:
/// Returns a vector of recently released buffers specified by tag
std::vector<Buffer::Tag> GetTagsAndReleaseBuffers(std::size_t max_count);
+ void SetVolume(float volume);
+
+ float GetVolume() const {
+ return game_volume;
+ }
+
/// Returns true if the stream is currently playing
bool IsPlaying() const {
return state == State::Playing;
@@ -94,6 +100,7 @@ private:
u32 sample_rate; ///< Sample rate of the stream
Format format; ///< Format of the stream
+ float game_volume = 1.0f; ///< The volume the game currently has set
ReleaseCallback release_callback; ///< Buffer release callback for the stream
State state{State::Stopped}; ///< Playback state of the stream
Core::Timing::EventType* release_event{}; ///< Core timing release event for the stream
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index bdd885273..198b3fe07 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -47,6 +47,7 @@ add_custom_command(OUTPUT scm_rev.cpp
"${VIDEO_CORE}/shader/decode/integer_set.cpp"
"${VIDEO_CORE}/shader/decode/integer_set_predicate.cpp"
"${VIDEO_CORE}/shader/decode/memory.cpp"
+ "${VIDEO_CORE}/shader/decode/texture.cpp"
"${VIDEO_CORE}/shader/decode/other.cpp"
"${VIDEO_CORE}/shader/decode/predicate_set_predicate.cpp"
"${VIDEO_CORE}/shader/decode/predicate_set_register.cpp"
@@ -55,6 +56,9 @@ add_custom_command(OUTPUT scm_rev.cpp
"${VIDEO_CORE}/shader/decode/video.cpp"
"${VIDEO_CORE}/shader/decode/xmad.cpp"
"${VIDEO_CORE}/shader/decode.cpp"
+ "${VIDEO_CORE}/shader/node.h"
+ "${VIDEO_CORE}/shader/node_helper.cpp"
+ "${VIDEO_CORE}/shader/node_helper.h"
"${VIDEO_CORE}/shader/shader_ir.cpp"
"${VIDEO_CORE}/shader/shader_ir.h"
"${VIDEO_CORE}/shader/track.cpp"
@@ -90,11 +94,18 @@ add_library(common STATIC
logging/log.h
logging/text_formatter.cpp
logging/text_formatter.h
+ lz4_compression.cpp
+ lz4_compression.h
math_util.h
+ memory_hook.cpp
+ memory_hook.h
microprofile.cpp
microprofile.h
microprofileui.h
misc.cpp
+ multi_level_queue.h
+ page_table.cpp
+ page_table.h
param_package.cpp
param_package.h
quaternion.h
@@ -113,8 +124,14 @@ add_library(common STATIC
threadsafe_queue.h
timer.cpp
timer.h
+ uint128.cpp
+ uint128.h
+ uuid.cpp
+ uuid.h
vector_math.h
web_result.h
+ zstd_compression.cpp
+ zstd_compression.h
)
if(ARCHITECTURE_x86_64)
@@ -128,3 +145,4 @@ endif()
create_target_directory_groups(common)
target_link_libraries(common PUBLIC Boost::boost fmt microprofile)
+target_link_libraries(common PRIVATE lz4_static libzstd_static)
diff --git a/src/common/assert.h b/src/common/assert.h
index 6002f7ab1..4b0e3f64e 100644
--- a/src/common/assert.h
+++ b/src/common/assert.h
@@ -57,3 +57,21 @@ __declspec(noinline, noreturn)
#define UNIMPLEMENTED_IF(cond) ASSERT_MSG(!(cond), "Unimplemented code!")
#define UNIMPLEMENTED_IF_MSG(cond, ...) ASSERT_MSG(!(cond), __VA_ARGS__)
+
+// If the assert is ignored, execute _b_
+#define ASSERT_OR_EXECUTE(_a_, _b_) \
+ do { \
+ ASSERT(_a_); \
+ if (!(_a_)) { \
+ _b_ \
+ } \
+ } while (0)
+
+// If the assert is ignored, execute _b_
+#define ASSERT_OR_EXECUTE_MSG(_a_, _b_, ...) \
+ do { \
+ ASSERT_MSG(_a_, __VA_ARGS__); \
+ if (!(_a_)) { \
+ _b_ \
+ } \
+ } while (0)
diff --git a/src/common/bit_field.h b/src/common/bit_field.h
index 21e07925d..902e668e3 100644
--- a/src/common/bit_field.h
+++ b/src/common/bit_field.h
@@ -34,6 +34,7 @@
#include <limits>
#include <type_traits>
#include "common/common_funcs.h"
+#include "common/swap.h"
/*
* Abstract bitfield class
@@ -108,15 +109,9 @@
* symptoms.
*/
#pragma pack(1)
-template <std::size_t Position, std::size_t Bits, typename T>
+template <std::size_t Position, std::size_t Bits, typename T, typename EndianTag = LETag>
struct BitField {
private:
- // We hide the copy assigment operator here, because the default copy
- // assignment would copy the full storage value, rather than just the bits
- // relevant to this particular bit field.
- // We don't delete it because we want BitField to be trivially copyable.
- constexpr BitField& operator=(const BitField&) = default;
-
// UnderlyingType is T for non-enum types and the underlying type of T if
// T is an enumeration. Note that T is wrapped within an enable_if in the
// former case to workaround compile errors which arise when using
@@ -127,6 +122,8 @@ private:
// We store the value as the unsigned type to avoid undefined behaviour on value shifting
using StorageType = std::make_unsigned_t<UnderlyingType>;
+ using StorageTypeWithEndian = typename AddEndian<StorageType, EndianTag>::type;
+
public:
/// Constants to allow limited introspection of fields if needed
static constexpr std::size_t position = Position;
@@ -163,16 +160,20 @@ public:
BitField(T val) = delete;
BitField& operator=(T val) = delete;
- // Force default constructor to be created
- // so that we can use this within unions
- constexpr BitField() = default;
+ constexpr BitField() noexcept = default;
+
+ constexpr BitField(const BitField&) noexcept = default;
+ constexpr BitField& operator=(const BitField&) noexcept = default;
+
+ constexpr BitField(BitField&&) noexcept = default;
+ constexpr BitField& operator=(BitField&&) noexcept = default;
constexpr FORCE_INLINE operator T() const {
return Value();
}
constexpr FORCE_INLINE void Assign(const T& value) {
- storage = (storage & ~mask) | FormatValue(value);
+ storage = (static_cast<StorageType>(storage) & ~mask) | FormatValue(value);
}
constexpr T Value() const {
@@ -184,7 +185,7 @@ public:
}
private:
- StorageType storage;
+ StorageTypeWithEndian storage;
static_assert(bits + position <= 8 * sizeof(T), "Bitfield out of range");
@@ -195,3 +196,6 @@ private:
static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable in a BitField");
};
#pragma pack()
+
+template <std::size_t Position, std::size_t Bits, typename T>
+using BitFieldBE = BitField<Position, Bits, T, BETag>;
diff --git a/src/common/bit_util.h b/src/common/bit_util.h
index 1eea17ba1..d032df413 100644
--- a/src/common/bit_util.h
+++ b/src/common/bit_util.h
@@ -32,7 +32,7 @@ inline u32 CountLeadingZeroes32(u32 value) {
return 32;
}
-inline u64 CountLeadingZeroes64(u64 value) {
+inline u32 CountLeadingZeroes64(u64 value) {
unsigned long leading_zero = 0;
if (_BitScanReverse64(&leading_zero, value) != 0) {
@@ -47,15 +47,54 @@ inline u32 CountLeadingZeroes32(u32 value) {
return 32;
}
- return __builtin_clz(value);
+ return static_cast<u32>(__builtin_clz(value));
}
-inline u64 CountLeadingZeroes64(u64 value) {
+inline u32 CountLeadingZeroes64(u64 value) {
if (value == 0) {
return 64;
}
- return __builtin_clzll(value);
+ return static_cast<u32>(__builtin_clzll(value));
}
#endif
+
+#ifdef _MSC_VER
+inline u32 CountTrailingZeroes32(u32 value) {
+ unsigned long trailing_zero = 0;
+
+ if (_BitScanForward(&trailing_zero, value) != 0) {
+ return trailing_zero;
+ }
+
+ return 32;
+}
+
+inline u32 CountTrailingZeroes64(u64 value) {
+ unsigned long trailing_zero = 0;
+
+ if (_BitScanForward64(&trailing_zero, value) != 0) {
+ return trailing_zero;
+ }
+
+ return 64;
+}
+#else
+inline u32 CountTrailingZeroes32(u32 value) {
+ if (value == 0) {
+ return 32;
+ }
+
+ return static_cast<u32>(__builtin_ctz(value));
+}
+
+inline u32 CountTrailingZeroes64(u64 value) {
+ if (value == 0) {
+ return 64;
+ }
+
+ return static_cast<u32>(__builtin_ctzll(value));
+}
+#endif
+
} // namespace Common
diff --git a/src/common/common_types.h b/src/common/common_types.h
index 6b1766dca..4cec89fbd 100644
--- a/src/common/common_types.h
+++ b/src/common/common_types.h
@@ -40,10 +40,9 @@ using s64 = std::int64_t; ///< 64-bit signed int
using f32 = float; ///< 32-bit floating point
using f64 = double; ///< 64-bit floating point
-// TODO: It would be nice to eventually replace these with strong types that prevent accidental
-// conversion between each other.
-using VAddr = u64; ///< Represents a pointer in the userspace virtual address space.
-using PAddr = u64; ///< Represents a pointer in the ARM11 physical address space.
+using VAddr = u64; ///< Represents a pointer in the userspace virtual address space.
+using PAddr = u64; ///< Represents a pointer in the ARM11 physical address space.
+using GPUVAddr = u64; ///< Represents a pointer in the GPU virtual address space.
using u128 = std::array<std::uint64_t, 2>;
static_assert(sizeof(u128) == 16, "u128 must be 128 bits wide");
diff --git a/src/common/detached_tasks.cpp b/src/common/detached_tasks.cpp
index a347d9e02..f268d6021 100644
--- a/src/common/detached_tasks.cpp
+++ b/src/common/detached_tasks.cpp
@@ -16,22 +16,22 @@ DetachedTasks::DetachedTasks() {
}
void DetachedTasks::WaitForAllTasks() {
- std::unique_lock<std::mutex> lock(mutex);
+ std::unique_lock lock{mutex};
cv.wait(lock, [this]() { return count == 0; });
}
DetachedTasks::~DetachedTasks() {
- std::unique_lock<std::mutex> lock(mutex);
+ std::unique_lock lock{mutex};
ASSERT(count == 0);
instance = nullptr;
}
void DetachedTasks::AddTask(std::function<void()> task) {
- std::unique_lock<std::mutex> lock(instance->mutex);
+ std::unique_lock lock{instance->mutex};
++instance->count;
std::thread([task{std::move(task)}]() {
task();
- std::unique_lock<std::mutex> lock(instance->mutex);
+ std::unique_lock lock{instance->mutex};
--instance->count;
std::notify_all_at_thread_exit(instance->cv, std::move(lock));
})
diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp
index aecb66c32..2d9374783 100644
--- a/src/common/file_util.cpp
+++ b/src/common/file_util.cpp
@@ -78,16 +78,17 @@ namespace FileUtil {
// Remove any ending forward slashes from directory paths
// Modifies argument.
static void StripTailDirSlashes(std::string& fname) {
- if (fname.length() > 1) {
- std::size_t i = fname.length();
- while (i > 0 && fname[i - 1] == DIR_SEP_CHR)
- --i;
- fname.resize(i);
+ if (fname.length() <= 1) {
+ return;
+ }
+
+ std::size_t i = fname.length();
+ while (i > 0 && fname[i - 1] == DIR_SEP_CHR) {
+ --i;
}
- return;
+ fname.resize(i);
}
-// Returns true if file filename exists
bool Exists(const std::string& filename) {
struct stat file_info;
@@ -107,7 +108,6 @@ bool Exists(const std::string& filename) {
return (result == 0);
}
-// Returns true if filename is a directory
bool IsDirectory(const std::string& filename) {
struct stat file_info;
@@ -132,8 +132,6 @@ bool IsDirectory(const std::string& filename) {
return S_ISDIR(file_info.st_mode);
}
-// Deletes a given filename, return true on success
-// Doesn't supports deleting a directory
bool Delete(const std::string& filename) {
LOG_TRACE(Common_Filesystem, "file {}", filename);
@@ -165,7 +163,6 @@ bool Delete(const std::string& filename) {
return true;
}
-// Returns true if successful, or path already exists.
bool CreateDir(const std::string& path) {
LOG_TRACE(Common_Filesystem, "directory {}", path);
#ifdef _WIN32
@@ -194,7 +191,6 @@ bool CreateDir(const std::string& path) {
#endif
}
-// Creates the full path of fullPath returns true on success
bool CreateFullPath(const std::string& fullPath) {
int panicCounter = 100;
LOG_TRACE(Common_Filesystem, "path {}", fullPath);
@@ -230,7 +226,6 @@ bool CreateFullPath(const std::string& fullPath) {
}
}
-// Deletes a directory filename, returns true on success
bool DeleteDir(const std::string& filename) {
LOG_TRACE(Common_Filesystem, "directory {}", filename);
@@ -252,7 +247,6 @@ bool DeleteDir(const std::string& filename) {
return false;
}
-// renames file srcFilename to destFilename, returns true on success
bool Rename(const std::string& srcFilename, const std::string& destFilename) {
LOG_TRACE(Common_Filesystem, "{} --> {}", srcFilename, destFilename);
#ifdef _WIN32
@@ -268,7 +262,6 @@ bool Rename(const std::string& srcFilename, const std::string& destFilename) {
return false;
}
-// copies file srcFilename to destFilename, returns true on success
bool Copy(const std::string& srcFilename, const std::string& destFilename) {
LOG_TRACE(Common_Filesystem, "{} --> {}", srcFilename, destFilename);
#ifdef _WIN32
@@ -324,7 +317,6 @@ bool Copy(const std::string& srcFilename, const std::string& destFilename) {
#endif
}
-// Returns the size of filename (64bit)
u64 GetSize(const std::string& filename) {
if (!Exists(filename)) {
LOG_ERROR(Common_Filesystem, "failed {}: No such file", filename);
@@ -351,7 +343,6 @@ u64 GetSize(const std::string& filename) {
return 0;
}
-// Overloaded GetSize, accepts file descriptor
u64 GetSize(const int fd) {
struct stat buf;
if (fstat(fd, &buf) != 0) {
@@ -361,7 +352,6 @@ u64 GetSize(const int fd) {
return buf.st_size;
}
-// Overloaded GetSize, accepts FILE*
u64 GetSize(FILE* f) {
// can't use off_t here because it can be 32-bit
u64 pos = ftello(f);
@@ -377,7 +367,6 @@ u64 GetSize(FILE* f) {
return size;
}
-// creates an empty file filename, returns true on success
bool CreateEmptyFile(const std::string& filename) {
LOG_TRACE(Common_Filesystem, "{}", filename);
@@ -502,7 +491,6 @@ bool DeleteDirRecursively(const std::string& directory, unsigned int recursion)
return true;
}
-// Create directory and copy contents (does not overwrite existing files)
void CopyDir(const std::string& source_path, const std::string& dest_path) {
#ifndef _WIN32
if (source_path == dest_path)
@@ -539,8 +527,7 @@ void CopyDir(const std::string& source_path, const std::string& dest_path) {
#endif
}
-// Returns the current directory
-std::string GetCurrentDir() {
+std::optional<std::string> GetCurrentDir() {
// Get the current working directory (getcwd uses malloc)
#ifdef _WIN32
wchar_t* dir;
@@ -550,7 +537,7 @@ std::string GetCurrentDir() {
if (!(dir = getcwd(nullptr, 0))) {
#endif
LOG_ERROR(Common_Filesystem, "GetCurrentDirectory failed: {}", GetLastErrorMsg());
- return nullptr;
+ return {};
}
#ifdef _WIN32
std::string strDir = Common::UTF16ToUTF8(dir);
@@ -561,7 +548,6 @@ std::string GetCurrentDir() {
return strDir;
}
-// Sets the current directory to the given directory
bool SetCurrentDir(const std::string& directory) {
#ifdef _WIN32
return _wchdir(Common::UTF8ToUTF16W(directory).c_str()) == 0;
@@ -673,8 +659,6 @@ std::string GetSysDirectory() {
return sysDir;
}
-// Returns a string with a yuzu data dir or file in the user's home
-// directory. To be used in "multi-user" mode (that is, installed).
const std::string& GetUserPath(UserPath path, const std::string& new_path) {
static std::unordered_map<UserPath, std::string> paths;
auto& user_path = paths[UserPath::UserDir];
@@ -762,11 +746,11 @@ std::string GetNANDRegistrationDir(bool system) {
return GetUserPath(UserPath::NANDDir) + "user/Contents/registered/";
}
-std::size_t WriteStringToFile(bool text_file, const std::string& str, const char* filename) {
- return FileUtil::IOFile(filename, text_file ? "w" : "wb").WriteBytes(str.data(), str.size());
+std::size_t WriteStringToFile(bool text_file, const std::string& filename, std::string_view str) {
+ return IOFile(filename, text_file ? "w" : "wb").WriteString(str);
}
-std::size_t ReadFileToString(bool text_file, const char* filename, std::string& str) {
+std::size_t ReadFileToString(bool text_file, const std::string& filename, std::string& str) {
IOFile file(filename, text_file ? "r" : "rb");
if (!file.IsOpen())
@@ -776,13 +760,6 @@ std::size_t ReadFileToString(bool text_file, const char* filename, std::string&
return file.ReadArray(&str[0], str.size());
}
-/**
- * Splits the filename into 8.3 format
- * Loosely implemented following https://en.wikipedia.org/wiki/8.3_filename
- * @param filename The normal filename to use
- * @param short_name A 9-char array in which the short name will be written
- * @param extension A 4-char array in which the extension will be written
- */
void SplitFilename83(const std::string& filename, std::array<char, 9>& short_name,
std::array<char, 4>& extension) {
const std::string forbidden_characters = ".\"/\\[]:;=, ";
diff --git a/src/common/file_util.h b/src/common/file_util.h
index 38cc7f059..cde7ddf2d 100644
--- a/src/common/file_util.h
+++ b/src/common/file_util.h
@@ -9,6 +9,7 @@
#include <fstream>
#include <functional>
#include <limits>
+#include <optional>
#include <string>
#include <string_view>
#include <type_traits>
@@ -118,7 +119,7 @@ u64 ScanDirectoryTree(const std::string& directory, FSTEntry& parent_entry,
bool DeleteDirRecursively(const std::string& directory, unsigned int recursion = 256);
// Returns the current directory
-std::string GetCurrentDir();
+std::optional<std::string> GetCurrentDir();
// Create directory and copy contents (does not overwrite existing files)
void CopyDir(const std::string& source_path, const std::string& dest_path);
@@ -146,9 +147,9 @@ const std::string& GetExeDirectory();
std::string AppDataRoamingDirectory();
#endif
-std::size_t WriteStringToFile(bool text_file, const std::string& str, const char* filename);
+std::size_t WriteStringToFile(bool text_file, const std::string& filename, std::string_view str);
-std::size_t ReadFileToString(bool text_file, const char* filename, std::string& str);
+std::size_t ReadFileToString(bool text_file, const std::string& filename, std::string& str);
/**
* Splits the filename into 8.3 format
@@ -257,8 +258,8 @@ public:
return WriteArray(&object, 1);
}
- std::size_t WriteString(const std::string& str) {
- return WriteArray(str.c_str(), str.length());
+ std::size_t WriteString(std::string_view str) {
+ return WriteArray(str.data(), str.length());
}
bool IsOpen() const {
@@ -286,8 +287,8 @@ private:
template <typename T>
void OpenFStream(T& fstream, const std::string& filename, std::ios_base::openmode openmode) {
#ifdef _MSC_VER
- fstream.open(Common::UTF8ToUTF16W(filename).c_str(), openmode);
+ fstream.open(Common::UTF8ToUTF16W(filename), openmode);
#else
- fstream.open(filename.c_str(), openmode);
+ fstream.open(filename, openmode);
#endif
}
diff --git a/src/common/hex_util.cpp b/src/common/hex_util.cpp
index 5b63f9e81..c2f6cf0f6 100644
--- a/src/common/hex_util.cpp
+++ b/src/common/hex_util.cpp
@@ -30,13 +30,6 @@ std::vector<u8> HexStringToVector(std::string_view str, bool little_endian) {
return out;
}
-std::string HexVectorToString(const std::vector<u8>& vector, bool upper) {
- std::string out;
- for (u8 c : vector)
- out += fmt::format(upper ? "{:02X}" : "{:02x}", c);
- return out;
-}
-
std::array<u8, 16> operator""_array16(const char* str, std::size_t len) {
if (len != 32) {
LOG_ERROR(Common,
diff --git a/src/common/hex_util.h b/src/common/hex_util.h
index 68f003cb6..bb4736f96 100644
--- a/src/common/hex_util.h
+++ b/src/common/hex_util.h
@@ -7,6 +7,7 @@
#include <array>
#include <cstddef>
#include <string>
+#include <type_traits>
#include <vector>
#include <fmt/format.h>
#include "common/common_types.h"
@@ -30,13 +31,20 @@ std::array<u8, Size> HexStringToArray(std::string_view str) {
return out;
}
-std::string HexVectorToString(const std::vector<u8>& vector, bool upper = true);
+template <typename ContiguousContainer>
+std::string HexToString(const ContiguousContainer& data, bool upper = true) {
+ static_assert(std::is_same_v<typename ContiguousContainer::value_type, u8>,
+ "Underlying type within the contiguous container must be u8.");
+
+ constexpr std::size_t pad_width = 2;
-template <std::size_t Size>
-std::string HexArrayToString(std::array<u8, Size> array, bool upper = true) {
std::string out;
- for (u8 c : array)
+ out.reserve(std::size(data) * pad_width);
+
+ for (const u8 c : data) {
out += fmt::format(upper ? "{:02X}" : "{:02x}", c);
+ }
+
return out;
}
diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp
index 4462ff3fb..a03179520 100644
--- a/src/common/logging/backend.cpp
+++ b/src/common/logging/backend.cpp
@@ -46,12 +46,12 @@ public:
}
void AddBackend(std::unique_ptr<Backend> backend) {
- std::lock_guard<std::mutex> lock(writing_mutex);
+ std::lock_guard lock{writing_mutex};
backends.push_back(std::move(backend));
}
void RemoveBackend(std::string_view backend_name) {
- std::lock_guard<std::mutex> lock(writing_mutex);
+ std::lock_guard lock{writing_mutex};
const auto it =
std::remove_if(backends.begin(), backends.end(),
[&backend_name](const auto& i) { return backend_name == i->GetName(); });
@@ -80,7 +80,7 @@ private:
backend_thread = std::thread([&] {
Entry entry;
auto write_logs = [&](Entry& e) {
- std::lock_guard<std::mutex> lock(writing_mutex);
+ std::lock_guard lock{writing_mutex};
for (const auto& backend : backends) {
backend->Write(e);
}
diff --git a/src/common/lz4_compression.cpp b/src/common/lz4_compression.cpp
new file mode 100644
index 000000000..ade6759bb
--- /dev/null
+++ b/src/common/lz4_compression.cpp
@@ -0,0 +1,76 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <lz4hc.h>
+
+#include "common/assert.h"
+#include "common/lz4_compression.h"
+
+namespace Common::Compression {
+
+std::vector<u8> CompressDataLZ4(const u8* source, std::size_t source_size) {
+ ASSERT_MSG(source_size <= LZ4_MAX_INPUT_SIZE, "Source size exceeds LZ4 maximum input size");
+
+ const auto source_size_int = static_cast<int>(source_size);
+ const int max_compressed_size = LZ4_compressBound(source_size_int);
+ std::vector<u8> compressed(max_compressed_size);
+
+ const int compressed_size = LZ4_compress_default(reinterpret_cast<const char*>(source),
+ reinterpret_cast<char*>(compressed.data()),
+ source_size_int, max_compressed_size);
+
+ if (compressed_size <= 0) {
+ // Compression failed
+ return {};
+ }
+
+ compressed.resize(compressed_size);
+
+ return compressed;
+}
+
+std::vector<u8> CompressDataLZ4HC(const u8* source, std::size_t source_size,
+ s32 compression_level) {
+ ASSERT_MSG(source_size <= LZ4_MAX_INPUT_SIZE, "Source size exceeds LZ4 maximum input size");
+
+ compression_level = std::clamp(compression_level, LZ4HC_CLEVEL_MIN, LZ4HC_CLEVEL_MAX);
+
+ const auto source_size_int = static_cast<int>(source_size);
+ const int max_compressed_size = LZ4_compressBound(source_size_int);
+ std::vector<u8> compressed(max_compressed_size);
+
+ const int compressed_size = LZ4_compress_HC(
+ reinterpret_cast<const char*>(source), reinterpret_cast<char*>(compressed.data()),
+ source_size_int, max_compressed_size, compression_level);
+
+ if (compressed_size <= 0) {
+ // Compression failed
+ return {};
+ }
+
+ compressed.resize(compressed_size);
+
+ return compressed;
+}
+
+std::vector<u8> CompressDataLZ4HCMax(const u8* source, std::size_t source_size) {
+ return CompressDataLZ4HC(source, source_size, LZ4HC_CLEVEL_MAX);
+}
+
+std::vector<u8> DecompressDataLZ4(const std::vector<u8>& compressed,
+ std::size_t uncompressed_size) {
+ std::vector<u8> uncompressed(uncompressed_size);
+ const int size_check = LZ4_decompress_safe(reinterpret_cast<const char*>(compressed.data()),
+ reinterpret_cast<char*>(uncompressed.data()),
+ static_cast<int>(compressed.size()),
+ static_cast<int>(uncompressed.size()));
+ if (static_cast<int>(uncompressed_size) != size_check) {
+ // Decompression failed
+ return {};
+ }
+ return uncompressed;
+}
+
+} // namespace Common::Compression
diff --git a/src/common/lz4_compression.h b/src/common/lz4_compression.h
new file mode 100644
index 000000000..4c16f6e03
--- /dev/null
+++ b/src/common/lz4_compression.h
@@ -0,0 +1,57 @@
+// Copyright 2019 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::Compression {
+
+/**
+ * Compresses a source memory region with LZ4 and returns the compressed data in a vector.
+ *
+ * @param source the uncompressed source memory region.
+ * @param source_size the size in bytes of the uncompressed source memory region.
+ *
+ * @return the compressed data.
+ */
+std::vector<u8> CompressDataLZ4(const u8* source, std::size_t source_size);
+
+/**
+ * Utilizes the LZ4 subalgorithm LZ4HC with the specified compression level. Higher compression
+ * levels result in a smaller compressed size, but require more CPU time for compression. The
+ * compression level has almost no impact on decompression speed. Data compressed with LZ4HC can
+ * also be decompressed with the default LZ4 decompression.
+ *
+ * @param source the uncompressed source memory region.
+ * @param source_size the size in bytes of the uncompressed source memory region.
+ * @param compression_level the used compression level. Should be between 3 and 12.
+ *
+ * @return the compressed data.
+ */
+std::vector<u8> CompressDataLZ4HC(const u8* source, std::size_t source_size, s32 compression_level);
+
+/**
+ * Utilizes the LZ4 subalgorithm LZ4HC with the highest possible compression level.
+ *
+ * @param source the uncompressed source memory region.
+ * @param source_size the size in bytes of the uncompressed source memory region.
+ *
+ * @return the compressed data.
+ */
+std::vector<u8> CompressDataLZ4HCMax(const u8* source, std::size_t source_size);
+
+/**
+ * Decompresses a source memory region with LZ4 and returns the uncompressed data in a vector.
+ *
+ * @param compressed the compressed source memory region.
+ * @param uncompressed_size the size in bytes of the uncompressed data.
+ *
+ * @return the decompressed data.
+ */
+std::vector<u8> DecompressDataLZ4(const std::vector<u8>& compressed, std::size_t uncompressed_size);
+
+} // namespace Common::Compression \ No newline at end of file
diff --git a/src/common/math_util.h b/src/common/math_util.h
index cff3d48c5..d6c35ee89 100644
--- a/src/common/math_util.h
+++ b/src/common/math_util.h
@@ -41,4 +41,7 @@ struct Rectangle {
}
};
+template <typename T>
+Rectangle(T, T, T, T)->Rectangle<T>;
+
} // namespace Common
diff --git a/src/core/memory_hook.cpp b/src/common/memory_hook.cpp
index c61c6c1fb..3986986d6 100644
--- a/src/core/memory_hook.cpp
+++ b/src/common/memory_hook.cpp
@@ -2,10 +2,10 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include "core/memory_hook.h"
+#include "common/memory_hook.h"
-namespace Memory {
+namespace Common {
MemoryHook::~MemoryHook() = default;
-} // namespace Memory
+} // namespace Common
diff --git a/src/core/memory_hook.h b/src/common/memory_hook.h
index 940777107..adaa4c2c5 100644
--- a/src/core/memory_hook.h
+++ b/src/common/memory_hook.h
@@ -9,7 +9,7 @@
#include "common/common_types.h"
-namespace Memory {
+namespace Common {
/**
* Memory hooks have two purposes:
@@ -44,4 +44,4 @@ public:
};
using MemoryHookPointer = std::shared_ptr<MemoryHook>;
-} // namespace Memory
+} // namespace Common
diff --git a/src/common/multi_level_queue.h b/src/common/multi_level_queue.h
new file mode 100644
index 000000000..9cb448f56
--- /dev/null
+++ b/src/common/multi_level_queue.h
@@ -0,0 +1,337 @@
+// 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);
+ }
+
+ std::size_t depth() const {
+ return Depth;
+ }
+
+ std::size_t size(u32 priority) const {
+ return levels[priority].size();
+ }
+
+ 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;
+ }
+
+ bool empty() const {
+ return used_priorities == 0;
+ }
+
+ bool empty(u32 priority) const {
+ return (used_priorities & (1ULL << priority)) == 0;
+ }
+
+ 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));
+ }
+
+ 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);
+ }
+
+ 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};
+ }
+ const_iterator begin(u32 max_prio = 0) const {
+ return cbegin(max_prio);
+ }
+ iterator begin(u32 max_prio = 0) {
+ const u32 priority = highest_priority_set(max_prio);
+ return priority == Depth ? end() : iterator{*this, levels[priority].begin(), priority};
+ }
+
+ const_iterator cend(u32 min_prio = Depth - 1) const {
+ return min_prio == Depth - 1 ? const_iterator{*this, Depth} : cbegin(min_prio + 1);
+ }
+ const_iterator end(u32 min_prio = Depth - 1) const {
+ return cend(min_prio);
+ }
+ iterator end(u32 min_prio = Depth - 1) {
+ return min_prio == Depth - 1 ? iterator{*this, Depth} : begin(min_prio + 1);
+ }
+
+ T& front(u32 max_priority = 0) {
+ const u32 priority = highest_priority_set(max_priority);
+ return levels[priority == Depth ? 0 : priority].front();
+ }
+ const T& front(u32 max_priority = 0) const {
+ const u32 priority = highest_priority_set(max_priority);
+ return levels[priority == Depth ? 0 : priority].front();
+ }
+
+ T back(u32 min_priority = Depth - 1) {
+ const u32 priority = lowest_priority_set(min_priority); // intended
+ return levels[priority == Depth ? 63 : priority].back();
+ }
+ 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();
+ }
+
+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);
+ }
+
+ 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/page_table.cpp b/src/common/page_table.cpp
new file mode 100644
index 000000000..69b7abc54
--- /dev/null
+++ b/src/common/page_table.cpp
@@ -0,0 +1,31 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/page_table.h"
+
+namespace Common {
+
+PageTable::PageTable(std::size_t page_size_in_bits) : page_size_in_bits{page_size_in_bits} {}
+
+PageTable::~PageTable() = default;
+
+void PageTable::Resize(std::size_t address_space_width_in_bits) {
+ const std::size_t num_page_table_entries = 1ULL
+ << (address_space_width_in_bits - page_size_in_bits);
+
+ pointers.resize(num_page_table_entries);
+ attributes.resize(num_page_table_entries);
+ backing_addr.resize(num_page_table_entries);
+
+ // The default is a 39-bit address space, which causes an initial 1GB allocation size. If the
+ // vector size is subsequently decreased (via resize), the vector might not automatically
+ // actually reallocate/resize its underlying allocation, which wastes up to ~800 MB for
+ // 36-bit titles. Call shrink_to_fit to reduce capacity to what's actually in use.
+
+ pointers.shrink_to_fit();
+ attributes.shrink_to_fit();
+ backing_addr.shrink_to_fit();
+}
+
+} // namespace Common
diff --git a/src/common/page_table.h b/src/common/page_table.h
new file mode 100644
index 000000000..8b8ff0bb8
--- /dev/null
+++ b/src/common/page_table.h
@@ -0,0 +1,84 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <vector>
+#include <boost/icl/interval_map.hpp>
+#include "common/common_types.h"
+#include "common/memory_hook.h"
+
+namespace Common {
+
+enum class PageType : u8 {
+ /// Page is unmapped and should cause an access error.
+ Unmapped,
+ /// Page is mapped to regular memory. This is the only type you can get pointers to.
+ Memory,
+ /// 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;
+
+ bool operator<(const SpecialRegion& other) const {
+ return std::tie(type, handler) < std::tie(other.type, other.handler);
+ }
+
+ bool operator==(const SpecialRegion& other) const {
+ return std::tie(type, handler) == std::tie(other.type, other.handler);
+ }
+};
+
+/**
+ * A (reasonably) fast way of allowing switchable and remappable process address spaces. It loosely
+ * mimics the way a real CPU page table works.
+ */
+struct PageTable {
+ explicit PageTable(std::size_t page_size_in_bits);
+ ~PageTable();
+
+ /**
+ * Resizes the page table to be able to accomodate enough pages within
+ * a given address space.
+ *
+ * @param address_space_width_in_bits The address size width in bits.
+ */
+ void Resize(std::size_t address_space_width_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`.
+ */
+ std::vector<u8*> pointers;
+
+ /**
+ * Contains MMIO handlers that back memory regions whose entries in the `attribute` vector is
+ * of type `Special`.
+ */
+ boost::icl::interval_map<u64, std::set<SpecialRegion>> special_regions;
+
+ /**
+ * Vector of fine grained page attributes. If it is set to any value other than `Memory`, then
+ * the corresponding entry in `pointers` MUST be set to null.
+ */
+ std::vector<PageType> attributes;
+
+ std::vector<u64> backing_addr;
+
+ const std::size_t page_size_in_bits{};
+};
+
+} // namespace Common
diff --git a/src/common/scope_exit.h b/src/common/scope_exit.h
index baf1f1c9e..1176a72b1 100644
--- a/src/common/scope_exit.h
+++ b/src/common/scope_exit.h
@@ -20,7 +20,7 @@ struct ScopeExitHelper {
template <typename Func>
ScopeExitHelper<Func> ScopeExit(Func&& func) {
- return ScopeExitHelper<Func>(std::move(func));
+ return ScopeExitHelper<Func>(std::forward<Func>(func));
}
} // namespace detail
diff --git a/src/common/swap.h b/src/common/swap.h
index 0e219747f..71932c2bb 100644
--- a/src/common/swap.h
+++ b/src/common/swap.h
@@ -17,13 +17,10 @@
#pragma once
+#include <type_traits>
+
#if defined(_MSC_VER)
#include <cstdlib>
-#elif defined(__linux__)
-#include <byteswap.h>
-#elif defined(__Bitrig__) || defined(__DragonFly__) || defined(__FreeBSD__) || \
- defined(__NetBSD__) || defined(__OpenBSD__)
-#include <sys/endian.h>
#endif
#include <cstring>
#include "common/common_types.h"
@@ -60,86 +57,49 @@
namespace Common {
#ifdef _MSC_VER
-inline u16 swap16(u16 _data) {
- return _byteswap_ushort(_data);
-}
-inline u32 swap32(u32 _data) {
- return _byteswap_ulong(_data);
-}
-inline u64 swap64(u64 _data) {
- return _byteswap_uint64(_data);
-}
-#elif defined(ARCHITECTURE_ARM) && (__ARM_ARCH >= 6)
-inline u16 swap16(u16 _data) {
- u32 data = _data;
- __asm__("rev16 %0, %1\n" : "=l"(data) : "l"(data));
- return (u16)data;
-}
-inline u32 swap32(u32 _data) {
- __asm__("rev %0, %1\n" : "=l"(_data) : "l"(_data));
- return _data;
-}
-inline u64 swap64(u64 _data) {
- return ((u64)swap32(_data) << 32) | swap32(_data >> 32);
-}
-#elif __linux__
-inline u16 swap16(u16 _data) {
- return bswap_16(_data);
-}
-inline u32 swap32(u32 _data) {
- return bswap_32(_data);
-}
-inline u64 swap64(u64 _data) {
- return bswap_64(_data);
+[[nodiscard]] inline u16 swap16(u16 data) noexcept {
+ return _byteswap_ushort(data);
}
-#elif __APPLE__
-inline __attribute__((always_inline)) u16 swap16(u16 _data) {
- return (_data >> 8) | (_data << 8);
+[[nodiscard]] inline u32 swap32(u32 data) noexcept {
+ return _byteswap_ulong(data);
}
-inline __attribute__((always_inline)) u32 swap32(u32 _data) {
- return __builtin_bswap32(_data);
+[[nodiscard]] inline u64 swap64(u64 data) noexcept {
+ return _byteswap_uint64(data);
}
-inline __attribute__((always_inline)) u64 swap64(u64 _data) {
- return __builtin_bswap64(_data);
-}
-#elif defined(__Bitrig__) || defined(__OpenBSD__)
+#elif defined(__clang__) || defined(__GNUC__)
+#if defined(__Bitrig__) || defined(__OpenBSD__)
// redefine swap16, swap32, swap64 as inline functions
#undef swap16
#undef swap32
#undef swap64
-inline u16 swap16(u16 _data) {
- return __swap16(_data);
-}
-inline u32 swap32(u32 _data) {
- return __swap32(_data);
-}
-inline u64 swap64(u64 _data) {
- return __swap64(_data);
-}
-#elif defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__)
-inline u16 swap16(u16 _data) {
- return bswap16(_data);
+#endif
+[[nodiscard]] inline u16 swap16(u16 data) noexcept {
+ return __builtin_bswap16(data);
}
-inline u32 swap32(u32 _data) {
- return bswap32(_data);
+[[nodiscard]] inline u32 swap32(u32 data) noexcept {
+ return __builtin_bswap32(data);
}
-inline u64 swap64(u64 _data) {
- return bswap64(_data);
+[[nodiscard]] inline u64 swap64(u64 data) noexcept {
+ return __builtin_bswap64(data);
}
#else
-// Slow generic implementation.
-inline u16 swap16(u16 data) {
+// Generic implementation.
+[[nodiscard]] inline u16 swap16(u16 data) noexcept {
return (data >> 8) | (data << 8);
}
-inline u32 swap32(u32 data) {
- return (swap16(data) << 16) | swap16(data >> 16);
+[[nodiscard]] inline u32 swap32(u32 data) noexcept {
+ return ((data & 0xFF000000U) >> 24) | ((data & 0x00FF0000U) >> 8) |
+ ((data & 0x0000FF00U) << 8) | ((data & 0x000000FFU) << 24);
}
-inline u64 swap64(u64 data) {
- return ((u64)swap32(data) << 32) | swap32(data >> 32);
+[[nodiscard]] inline u64 swap64(u64 data) noexcept {
+ return ((data & 0xFF00000000000000ULL) >> 56) | ((data & 0x00FF000000000000ULL) >> 40) |
+ ((data & 0x0000FF0000000000ULL) >> 24) | ((data & 0x000000FF00000000ULL) >> 8) |
+ ((data & 0x00000000FF000000ULL) << 8) | ((data & 0x0000000000FF0000ULL) << 24) |
+ ((data & 0x000000000000FF00ULL) << 40) | ((data & 0x00000000000000FFULL) << 56);
}
#endif
-inline float swapf(float f) {
+[[nodiscard]] inline float swapf(float f) noexcept {
static_assert(sizeof(u32) == sizeof(float), "float must be the same size as uint32_t.");
u32 value;
@@ -151,7 +111,7 @@ inline float swapf(float f) {
return f;
}
-inline double swapd(double f) {
+[[nodiscard]] inline double swapd(double f) noexcept {
static_assert(sizeof(u64) == sizeof(double), "double must be the same size as uint64_t.");
u64 value;
@@ -170,7 +130,7 @@ struct swap_struct_t {
using swapped_t = swap_struct_t;
protected:
- T value = T();
+ T value;
static T swap(T v) {
return F::swap(v);
@@ -605,52 +565,154 @@ struct swap_double_t {
}
};
-#if COMMON_LITTLE_ENDIAN
-using u16_le = u16;
-using u32_le = u32;
-using u64_le = u64;
+template <typename T>
+struct swap_enum_t {
+ static_assert(std::is_enum_v<T>);
+ using base = std::underlying_type_t<T>;
-using s16_le = s16;
-using s32_le = s32;
-using s64_le = s64;
+public:
+ swap_enum_t() = default;
+ swap_enum_t(const T& v) : value(swap(v)) {}
-using float_le = float;
-using double_le = double;
+ swap_enum_t& operator=(const T& v) {
+ value = swap(v);
+ return *this;
+ }
-using u64_be = swap_struct_t<u64, swap_64_t<u64>>;
-using s64_be = swap_struct_t<s64, swap_64_t<s64>>;
+ operator T() const {
+ return swap(value);
+ }
-using u32_be = swap_struct_t<u32, swap_32_t<u32>>;
-using s32_be = swap_struct_t<s32, swap_32_t<s32>>;
+ explicit operator base() const {
+ return static_cast<base>(swap(value));
+ }
-using u16_be = swap_struct_t<u16, swap_16_t<u16>>;
-using s16_be = swap_struct_t<s16, swap_16_t<s16>>;
+protected:
+ T value{};
+ // clang-format off
+ using swap_t = std::conditional_t<
+ std::is_same_v<base, u16>, swap_16_t<u16>, std::conditional_t<
+ std::is_same_v<base, s16>, swap_16_t<s16>, std::conditional_t<
+ std::is_same_v<base, u32>, swap_32_t<u32>, std::conditional_t<
+ std::is_same_v<base, s32>, swap_32_t<s32>, std::conditional_t<
+ std::is_same_v<base, u64>, swap_64_t<u64>, std::conditional_t<
+ std::is_same_v<base, s64>, swap_64_t<s64>, void>>>>>>;
+ // clang-format on
+ static T swap(T x) {
+ return static_cast<T>(swap_t::swap(static_cast<base>(x)));
+ }
+};
-using float_be = swap_struct_t<float, swap_float_t<float>>;
-using double_be = swap_struct_t<double, swap_double_t<double>>;
-#else
+struct SwapTag {}; // Use the different endianness from the system
+struct KeepTag {}; // Use the same endianness as the system
-using u64_le = swap_struct_t<u64, swap_64_t<u64>>;
-using s64_le = swap_struct_t<s64, swap_64_t<s64>>;
+template <typename T, typename Tag>
+struct AddEndian;
-using u32_le = swap_struct_t<u32, swap_32_t<u32>>;
-using s32_le = swap_struct_t<s32, swap_32_t<s32>>;
+// KeepTag specializations
-using u16_le = swap_struct_t<u16, swap_16_t<u16>>;
-using s16_le = swap_struct_t<s16, swap_16_t<s16>>;
+template <typename T>
+struct AddEndian<T, KeepTag> {
+ using type = T;
+};
-using float_le = swap_struct_t<float, swap_float_t<float>>;
-using double_le = swap_struct_t<double, swap_double_t<double>>;
+// SwapTag specializations
-using u16_be = u16;
-using u32_be = u32;
-using u64_be = u64;
+template <>
+struct AddEndian<u8, SwapTag> {
+ using type = u8;
+};
-using s16_be = s16;
-using s32_be = s32;
-using s64_be = s64;
+template <>
+struct AddEndian<u16, SwapTag> {
+ using type = swap_struct_t<u16, swap_16_t<u16>>;
+};
-using float_be = float;
-using double_be = double;
+template <>
+struct AddEndian<u32, SwapTag> {
+ using type = swap_struct_t<u32, swap_32_t<u32>>;
+};
+
+template <>
+struct AddEndian<u64, SwapTag> {
+ using type = swap_struct_t<u64, swap_64_t<u64>>;
+};
+
+template <>
+struct AddEndian<s8, SwapTag> {
+ using type = s8;
+};
+
+template <>
+struct AddEndian<s16, SwapTag> {
+ using type = swap_struct_t<s16, swap_16_t<s16>>;
+};
+
+template <>
+struct AddEndian<s32, SwapTag> {
+ using type = swap_struct_t<s32, swap_32_t<s32>>;
+};
+
+template <>
+struct AddEndian<s64, SwapTag> {
+ using type = swap_struct_t<s64, swap_64_t<s64>>;
+};
+
+template <>
+struct AddEndian<float, SwapTag> {
+ using type = swap_struct_t<float, swap_float_t<float>>;
+};
+
+template <>
+struct AddEndian<double, SwapTag> {
+ using type = swap_struct_t<double, swap_double_t<double>>;
+};
+
+template <typename T>
+struct AddEndian<T, SwapTag> {
+ static_assert(std::is_enum_v<T>);
+ using type = swap_enum_t<T>;
+};
+
+// Alias LETag/BETag as KeepTag/SwapTag depending on the system
+#if COMMON_LITTLE_ENDIAN
+
+using LETag = KeepTag;
+using BETag = SwapTag;
+
+#else
+
+using BETag = KeepTag;
+using LETag = SwapTag;
#endif
+
+// Aliases for LE types
+using u16_le = AddEndian<u16, LETag>::type;
+using u32_le = AddEndian<u32, LETag>::type;
+using u64_le = AddEndian<u64, LETag>::type;
+
+using s16_le = AddEndian<s16, LETag>::type;
+using s32_le = AddEndian<s32, LETag>::type;
+using s64_le = AddEndian<s64, LETag>::type;
+
+template <typename T>
+using enum_le = std::enable_if_t<std::is_enum_v<T>, typename AddEndian<T, LETag>::type>;
+
+using float_le = AddEndian<float, LETag>::type;
+using double_le = AddEndian<double, LETag>::type;
+
+// Aliases for BE types
+using u16_be = AddEndian<u16, BETag>::type;
+using u32_be = AddEndian<u32, BETag>::type;
+using u64_be = AddEndian<u64, BETag>::type;
+
+using s16_be = AddEndian<s16, BETag>::type;
+using s32_be = AddEndian<s32, BETag>::type;
+using s64_be = AddEndian<s64, BETag>::type;
+
+template <typename T>
+using enum_be = std::enable_if_t<std::is_enum_v<T>, typename AddEndian<T, BETag>::type>;
+
+using float_be = AddEndian<float, BETag>::type;
+using double_be = AddEndian<double, BETag>::type;
diff --git a/src/common/thread.cpp b/src/common/thread.cpp
index 5144c0d9f..fe7a420cc 100644
--- a/src/common/thread.cpp
+++ b/src/common/thread.cpp
@@ -27,18 +27,6 @@ namespace Common {
#ifdef _MSC_VER
-void SetThreadAffinity(std::thread::native_handle_type thread, u32 mask) {
- SetThreadAffinityMask(thread, mask);
-}
-
-void SetCurrentThreadAffinity(u32 mask) {
- SetThreadAffinityMask(GetCurrentThread(), mask);
-}
-
-void SwitchCurrentThread() {
- SwitchToThread();
-}
-
// Sets the debugger-visible name of the current thread.
// Uses undocumented (actually, it is now documented) trick.
// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vsdebug/html/vxtsksettingthreadname.asp
@@ -70,31 +58,6 @@ void SetCurrentThreadName(const char* name) {
#else // !MSVC_VER, so must be POSIX threads
-void SetThreadAffinity(std::thread::native_handle_type thread, u32 mask) {
-#ifdef __APPLE__
- thread_policy_set(pthread_mach_thread_np(thread), THREAD_AFFINITY_POLICY, (integer_t*)&mask, 1);
-#elif (defined __linux__ || defined __FreeBSD__) && !(defined ANDROID)
- cpu_set_t cpu_set;
- CPU_ZERO(&cpu_set);
-
- for (int i = 0; i != sizeof(mask) * 8; ++i)
- if ((mask >> i) & 1)
- CPU_SET(i, &cpu_set);
-
- pthread_setaffinity_np(thread, sizeof(cpu_set), &cpu_set);
-#endif
-}
-
-void SetCurrentThreadAffinity(u32 mask) {
- SetThreadAffinity(pthread_self(), mask);
-}
-
-#ifndef _WIN32
-void SwitchCurrentThread() {
- usleep(1000 * 1);
-}
-#endif
-
// MinGW with the POSIX threading model does not support pthread_setname_np
#if !defined(_WIN32) || defined(_MSC_VER)
void SetCurrentThreadName(const char* name) {
diff --git a/src/common/thread.h b/src/common/thread.h
index 2cf74452d..0cfd98be6 100644
--- a/src/common/thread.h
+++ b/src/common/thread.h
@@ -9,14 +9,13 @@
#include <cstddef>
#include <mutex>
#include <thread>
-#include "common/common_types.h"
namespace Common {
class Event {
public:
void Set() {
- std::lock_guard<std::mutex> lk(mutex);
+ std::lock_guard lk{mutex};
if (!is_set) {
is_set = true;
condvar.notify_one();
@@ -24,14 +23,14 @@ public:
}
void Wait() {
- std::unique_lock<std::mutex> lk(mutex);
+ std::unique_lock lk{mutex};
condvar.wait(lk, [&] { return is_set; });
is_set = false;
}
template <class Clock, class Duration>
bool WaitUntil(const std::chrono::time_point<Clock, Duration>& time) {
- std::unique_lock<std::mutex> lk(mutex);
+ std::unique_lock lk{mutex};
if (!condvar.wait_until(lk, time, [this] { return is_set; }))
return false;
is_set = false;
@@ -39,7 +38,7 @@ public:
}
void Reset() {
- std::unique_lock<std::mutex> lk(mutex);
+ std::unique_lock lk{mutex};
// no other action required, since wait loops on the predicate and any lingering signal will
// get cleared on the first iteration
is_set = false;
@@ -57,7 +56,7 @@ public:
/// Blocks until all "count" threads have called Sync()
void Sync() {
- std::unique_lock<std::mutex> lk(mutex);
+ std::unique_lock lk{mutex};
const std::size_t current_generation = generation;
if (++waiting == count) {
@@ -78,9 +77,6 @@ private:
std::size_t generation = 0; // Incremented once each time the barrier is used
};
-void SetThreadAffinity(std::thread::native_handle_type thread, u32 mask);
-void SetCurrentThreadAffinity(u32 mask);
-void SwitchCurrentThread(); // On Linux, this is equal to sleep 1ms
void SetCurrentThreadName(const char* name);
} // namespace Common
diff --git a/src/common/thread_queue_list.h b/src/common/thread_queue_list.h
index e7594db68..791f99a8c 100644
--- a/src/common/thread_queue_list.h
+++ b/src/common/thread_queue_list.h
@@ -6,7 +6,6 @@
#include <array>
#include <deque>
-#include <boost/range/algorithm_ext/erase.hpp>
namespace Common {
@@ -111,8 +110,9 @@ struct ThreadQueueList {
}
void remove(Priority priority, const T& thread_id) {
- Queue* cur = &queues[priority];
- boost::remove_erase(cur->data, thread_id);
+ Queue* const cur = &queues[priority];
+ const auto iter = std::remove(cur->data.begin(), cur->data.end(), thread_id);
+ cur->data.erase(iter, cur->data.end());
}
void rotate(Priority priority) {
diff --git a/src/common/threadsafe_queue.h b/src/common/threadsafe_queue.h
index 821e8536a..e714ba5b3 100644
--- a/src/common/threadsafe_queue.h
+++ b/src/common/threadsafe_queue.h
@@ -78,7 +78,7 @@ public:
T PopWait() {
if (Empty()) {
- std::unique_lock<std::mutex> lock(cv_mutex);
+ std::unique_lock lock{cv_mutex};
cv.wait(lock, [this]() { return !Empty(); });
}
T t;
@@ -137,7 +137,7 @@ public:
template <typename Arg>
void Push(Arg&& t) {
- std::lock_guard<std::mutex> lock(write_lock);
+ std::lock_guard lock{write_lock};
spsc_queue.Push(t);
}
diff --git a/src/common/uint128.cpp b/src/common/uint128.cpp
new file mode 100644
index 000000000..32bf56730
--- /dev/null
+++ b/src/common/uint128.cpp
@@ -0,0 +1,45 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#ifdef _MSC_VER
+#include <intrin.h>
+
+#pragma intrinsic(_umul128)
+#endif
+#include <cstring>
+#include "common/uint128.h"
+
+namespace Common {
+
+u128 Multiply64Into128(u64 a, u64 b) {
+ u128 result;
+#ifdef _MSC_VER
+ result[0] = _umul128(a, b, &result[1]);
+#else
+ unsigned __int128 tmp = a;
+ tmp *= b;
+ std::memcpy(&result, &tmp, sizeof(u128));
+#endif
+ return result;
+}
+
+std::pair<u64, u64> Divide128On32(u128 dividend, u32 divisor) {
+ u64 remainder = dividend[0] % divisor;
+ u64 accum = dividend[0] / divisor;
+ if (dividend[1] == 0)
+ return {accum, remainder};
+ // We ignore dividend[1] / divisor as that overflows
+ const u64 first_segment = (dividend[1] % divisor) << 32;
+ accum += (first_segment / divisor) << 32;
+ const u64 second_segment = (first_segment % divisor) << 32;
+ accum += (second_segment / divisor);
+ remainder += second_segment % divisor;
+ if (remainder >= divisor) {
+ accum++;
+ remainder -= divisor;
+ }
+ return {accum, remainder};
+}
+
+} // namespace Common
diff --git a/src/common/uint128.h b/src/common/uint128.h
new file mode 100644
index 000000000..a3be2a2cb
--- /dev/null
+++ b/src/common/uint128.h
@@ -0,0 +1,19 @@
+// 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/common_types.h"
+
+namespace Common {
+
+// This function multiplies 2 u64 values and produces a u128 value;
+u128 Multiply64Into128(u64 a, u64 b);
+
+// This function divides a u128 by a u32 value and produces two u64 values:
+// the result of division and the remainder
+std::pair<u64, u64> Divide128On32(u128 dividend, u32 divisor);
+
+} // namespace Common
diff --git a/src/common/uuid.cpp b/src/common/uuid.cpp
new file mode 100644
index 000000000..26db03fba
--- /dev/null
+++ b/src/common/uuid.cpp
@@ -0,0 +1,33 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <random>
+
+#include <fmt/format.h>
+
+#include "common/uuid.h"
+
+namespace Common {
+
+UUID UUID::Generate() {
+ std::random_device device;
+ std::mt19937 gen(device());
+ std::uniform_int_distribution<u64> distribution(1, std::numeric_limits<u64>::max());
+ return UUID{distribution(gen), distribution(gen)};
+}
+
+std::string UUID::Format() const {
+ return fmt::format("0x{:016X}{:016X}", uuid[1], uuid[0]);
+}
+
+std::string UUID::FormatSwitch() const {
+ std::array<u8, 16> s{};
+ std::memcpy(s.data(), uuid.data(), sizeof(u128));
+ return fmt::format("{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{"
+ ":02x}{:02x}{:02x}{:02x}{:02x}",
+ s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7], s[8], s[9], s[10], s[11],
+ s[12], s[13], s[14], s[15]);
+}
+
+} // namespace Common
diff --git a/src/common/uuid.h b/src/common/uuid.h
new file mode 100644
index 000000000..f6ad064fb
--- /dev/null
+++ b/src/common/uuid.h
@@ -0,0 +1,48 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <string>
+
+#include "common/common_types.h"
+
+namespace Common {
+
+constexpr u128 INVALID_UUID{{0, 0}};
+
+struct UUID {
+ // UUIDs which are 0 are considered invalid!
+ u128 uuid = INVALID_UUID;
+ constexpr UUID() = default;
+ constexpr explicit UUID(const u128& id) : uuid{id} {}
+ constexpr explicit UUID(const u64 lo, const u64 hi) : uuid{{lo, hi}} {}
+
+ constexpr explicit operator bool() const {
+ return uuid[0] != INVALID_UUID[0] && uuid[1] != INVALID_UUID[1];
+ }
+
+ constexpr bool operator==(const UUID& rhs) const {
+ // TODO(DarkLordZach): Replace with uuid == rhs.uuid with C++20
+ return uuid[0] == rhs.uuid[0] && uuid[1] == rhs.uuid[1];
+ }
+
+ constexpr bool operator!=(const UUID& rhs) const {
+ return !operator==(rhs);
+ }
+
+ // TODO(ogniK): Properly generate uuids based on RFC-4122
+ static UUID Generate();
+
+ // Set the UUID to {0,0} to be considered an invalid user
+ constexpr void Invalidate() {
+ uuid = INVALID_UUID;
+ }
+
+ std::string Format() const;
+ std::string FormatSwitch() const;
+};
+static_assert(sizeof(UUID) == 16, "UUID is an invalid size!");
+
+} // namespace Common
diff --git a/src/common/zstd_compression.cpp b/src/common/zstd_compression.cpp
new file mode 100644
index 000000000..978526492
--- /dev/null
+++ b/src/common/zstd_compression.cpp
@@ -0,0 +1,51 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <zstd.h>
+
+#include "common/assert.h"
+#include "common/zstd_compression.h"
+
+namespace Common::Compression {
+
+std::vector<u8> CompressDataZSTD(const u8* source, std::size_t source_size, s32 compression_level) {
+ compression_level = std::clamp(compression_level, 1, ZSTD_maxCLevel());
+
+ const std::size_t max_compressed_size = ZSTD_compressBound(source_size);
+ std::vector<u8> compressed(max_compressed_size);
+
+ const std::size_t compressed_size =
+ ZSTD_compress(compressed.data(), compressed.size(), source, source_size, compression_level);
+
+ if (ZSTD_isError(compressed_size)) {
+ // Compression failed
+ return {};
+ }
+
+ compressed.resize(compressed_size);
+
+ return compressed;
+}
+
+std::vector<u8> CompressDataZSTDDefault(const u8* source, std::size_t source_size) {
+ return CompressDataZSTD(source, source_size, ZSTD_CLEVEL_DEFAULT);
+}
+
+std::vector<u8> DecompressDataZSTD(const std::vector<u8>& compressed) {
+ const std::size_t decompressed_size =
+ ZSTD_getDecompressedSize(compressed.data(), compressed.size());
+ std::vector<u8> decompressed(decompressed_size);
+
+ const std::size_t uncompressed_result_size = ZSTD_decompress(
+ decompressed.data(), decompressed.size(), compressed.data(), compressed.size());
+
+ if (decompressed_size != uncompressed_result_size || ZSTD_isError(uncompressed_result_size)) {
+ // Decompression failed
+ return {};
+ }
+ return decompressed;
+}
+
+} // namespace Common::Compression
diff --git a/src/common/zstd_compression.h b/src/common/zstd_compression.h
new file mode 100644
index 000000000..e9de941c8
--- /dev/null
+++ b/src/common/zstd_compression.h
@@ -0,0 +1,44 @@
+// Copyright 2019 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::Compression {
+
+/**
+ * Compresses a source memory region with Zstandard and returns the compressed data in a vector.
+ *
+ * @param source the uncompressed source memory region.
+ * @param source_size the size in bytes of the uncompressed source memory region.
+ * @param compression_level the used compression level. Should be between 1 and 22.
+ *
+ * @return the compressed data.
+ */
+std::vector<u8> CompressDataZSTD(const u8* source, std::size_t source_size, s32 compression_level);
+
+/**
+ * Compresses a source memory region with Zstandard with the default compression level and returns
+ * the compressed data in a vector.
+ *
+ * @param source the uncompressed source memory region.
+ * @param source_size the size in bytes of the uncompressed source memory region.
+ *
+ * @return the compressed data.
+ */
+std::vector<u8> CompressDataZSTDDefault(const u8* source, std::size_t source_size);
+
+/**
+ * Decompresses a source memory region with Zstandard and returns the uncompressed data in a vector.
+ *
+ * @param compressed the compressed source memory region.
+ *
+ * @return the decompressed data.
+ */
+std::vector<u8> DecompressDataZSTD(const std::vector<u8>& compressed);
+
+} // namespace Common::Compression \ No newline at end of file
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 8ccb2d5f0..a2e2e976e 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -5,6 +5,8 @@ add_library(core STATIC
arm/exclusive_monitor.h
arm/unicorn/arm_unicorn.cpp
arm/unicorn/arm_unicorn.h
+ constants.cpp
+ constants.h
core.cpp
core.h
core_cpu.cpp
@@ -31,6 +33,8 @@ add_library(core STATIC
file_sys/bis_factory.h
file_sys/card_image.cpp
file_sys/card_image.h
+ file_sys/cheat_engine.cpp
+ file_sys/cheat_engine.h
file_sys/content_archive.cpp
file_sys/content_archive.h
file_sys/control_metadata.cpp
@@ -68,6 +72,8 @@ add_library(core STATIC
file_sys/system_archive/ng_word.h
file_sys/system_archive/system_archive.cpp
file_sys/system_archive/system_archive.h
+ file_sys/system_archive/system_version.cpp
+ file_sys/system_archive/system_version.h
file_sys/vfs.cpp
file_sys/vfs.h
file_sys/vfs_concat.cpp
@@ -84,6 +90,10 @@ add_library(core STATIC
file_sys/vfs_vector.h
file_sys/xts_archive.cpp
file_sys/xts_archive.h
+ frontend/applets/error.cpp
+ frontend/applets/error.h
+ frontend/applets/general_frontend.cpp
+ frontend/applets/general_frontend.h
frontend/applets/profile_select.cpp
frontend/applets/profile_select.h
frontend/applets/software_keyboard.cpp
@@ -107,6 +117,8 @@ add_library(core STATIC
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/handle_table.cpp
hle/kernel/handle_table.h
@@ -140,6 +152,8 @@ add_library(core STATIC
hle/kernel/svc_wrap.h
hle/kernel/thread.cpp
hle/kernel/thread.h
+ hle/kernel/transfer_memory.cpp
+ hle/kernel/transfer_memory.h
hle/kernel/vm_manager.cpp
hle/kernel/vm_manager.h
hle/kernel/wait_object.cpp
@@ -169,12 +183,14 @@ 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/error.cpp
+ hle/service/am/applets/error.h
+ hle/service/am/applets/general_backend.cpp
+ hle/service/am/applets/general_backend.h
hle/service/am/applets/profile_select.cpp
hle/service/am/applets/profile_select.h
hle/service/am/applets/software_keyboard.cpp
hle/service/am/applets/software_keyboard.h
- hle/service/am/applets/stub_applet.cpp
- hle/service/am/applets/stub_applet.h
hle/service/am/applets/web_browser.cpp
hle/service/am/applets/web_browser.h
hle/service/am/idle.cpp
@@ -296,6 +312,8 @@ add_library(core STATIC
hle/service/mig/mig.h
hle/service/mii/mii.cpp
hle/service/mii/mii.h
+ hle/service/mii/mii_manager.cpp
+ hle/service/mii/mii_manager.h
hle/service/mm/mm_u.cpp
hle/service/mm/mm_u.h
hle/service/ncm/ncm.cpp
@@ -312,6 +330,9 @@ add_library(core STATIC
hle/service/nim/nim.h
hle/service/npns/npns.cpp
hle/service/npns/npns.h
+ hle/service/ns/errors.h
+ hle/service/ns/language.cpp
+ hle/service/ns/language.h
hle/service/ns/ns.cpp
hle/service/ns/ns.h
hle/service/ns/pl_u.cpp
@@ -419,8 +440,6 @@ add_library(core STATIC
loader/deconstructed_rom_directory.h
loader/elf.cpp
loader/elf.h
- loader/linker.cpp
- loader/linker.h
loader/loader.cpp
loader/loader.h
loader/nax.cpp
@@ -437,8 +456,6 @@ add_library(core STATIC
loader/xci.h
memory.cpp
memory.h
- memory_hook.cpp
- memory_hook.h
memory_setup.h
perf_stats.cpp
perf_stats.h
@@ -454,7 +471,7 @@ add_library(core STATIC
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 lz4_static mbedtls opus unicorn open_source_archives)
+target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt mbedtls opus unicorn open_source_archives)
if (ENABLE_WEB_SERVICE)
target_compile_definitions(core PRIVATE -DENABLE_WEB_SERVICE)
target_link_libraries(core PRIVATE web_service)
diff --git a/src/core/arm/arm_interface.h b/src/core/arm/arm_interface.h
index 4dfd41b43..978b1518f 100644
--- a/src/core/arm/arm_interface.h
+++ b/src/core/arm/arm_interface.h
@@ -7,6 +7,10 @@
#include <array>
#include "common/common_types.h"
+namespace Common {
+struct PageTable;
+}
+
namespace Kernel {
enum class VMAPermission : u8;
}
@@ -49,8 +53,14 @@ public:
/// Clear all instruction cache
virtual void ClearInstructionCache() = 0;
- /// Notify CPU emulation that page tables have changed
- virtual void PageTableChanged() = 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;
/**
* Set the Program Counter to an address
diff --git a/src/core/arm/dynarmic/arm_dynarmic.cpp b/src/core/arm/dynarmic/arm_dynarmic.cpp
index 9b7ca4030..44307fa19 100644
--- a/src/core/arm/dynarmic/arm_dynarmic.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic.cpp
@@ -12,6 +12,7 @@
#include "core/core.h"
#include "core/core_cpu.h"
#include "core/core_timing.h"
+#include "core/core_timing_util.h"
#include "core/gdbstub/gdbstub.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/svc.h"
@@ -25,7 +26,6 @@ using Vector = Dynarmic::A64::Vector;
class ARM_Dynarmic_Callbacks : public Dynarmic::A64::UserCallbacks {
public:
explicit ARM_Dynarmic_Callbacks(ARM_Dynarmic& parent) : parent(parent) {}
- ~ARM_Dynarmic_Callbacks() = default;
u8 MemoryRead8(u64 vaddr) override {
return Memory::Read8(vaddr);
@@ -99,7 +99,7 @@ public:
}
void CallSVC(u32 swi) override {
- Kernel::CallSVC(swi);
+ Kernel::CallSVC(parent.system, swi);
}
void AddTicks(u64 ticks) override {
@@ -112,14 +112,14 @@ public:
// Always execute at least one tick.
amortized_ticks = std::max<u64>(amortized_ticks, 1);
- parent.core_timing.AddTicks(amortized_ticks);
+ parent.system.CoreTiming().AddTicks(amortized_ticks);
num_interpreted_instructions = 0;
}
u64 GetTicksRemaining() override {
- return std::max(parent.core_timing.GetDowncount(), 0);
+ return std::max(parent.system.CoreTiming().GetDowncount(), 0);
}
u64 GetCNTPCT() override {
- return parent.core_timing.GetTicks();
+ return Timing::CpuCyclesToClockCycles(parent.system.CoreTiming().GetTicks());
}
ARM_Dynarmic& parent;
@@ -128,18 +128,16 @@ public:
u64 tpidr_el0 = 0;
};
-std::unique_ptr<Dynarmic::A64::Jit> ARM_Dynarmic::MakeJit() const {
- auto* current_process = Core::CurrentProcess();
- auto** const page_table = current_process->VMManager().page_table.pointers.data();
-
+std::unique_ptr<Dynarmic::A64::Jit> ARM_Dynarmic::MakeJit(Common::PageTable& page_table,
+ std::size_t address_space_bits) const {
Dynarmic::A64::UserConfig config;
// Callbacks
config.callbacks = cb.get();
// Memory
- config.page_table = reinterpret_cast<void**>(page_table);
- config.page_table_address_space_bits = current_process->VMManager().GetAddressSpaceWidth();
+ config.page_table = reinterpret_cast<void**>(page_table.pointers.data());
+ config.page_table_address_space_bits = address_space_bits;
config.silently_mirror_page_table = false;
// Multi-process state
@@ -151,7 +149,7 @@ std::unique_ptr<Dynarmic::A64::Jit> ARM_Dynarmic::MakeJit() const {
config.tpidr_el0 = &cb->tpidr_el0;
config.dczid_el0 = 4;
config.ctr_el0 = 0x8444c004;
- config.cntfrq_el0 = 19200000; // Value from fusee.
+ config.cntfrq_el0 = Timing::CNTFREQ;
// Unpredictable instructions
config.define_unpredictable_behaviour = true;
@@ -163,7 +161,6 @@ MICROPROFILE_DEFINE(ARM_Jit_Dynarmic, "ARM JIT", "Dynarmic", MP_RGB(255, 64, 64)
void ARM_Dynarmic::Run() {
MICROPROFILE_SCOPE(ARM_Jit_Dynarmic);
- ASSERT(Memory::GetCurrentPageTable() == current_page_table);
jit->Run();
}
@@ -172,16 +169,11 @@ void ARM_Dynarmic::Step() {
cb->InterpreterFallback(jit->GetPC(), 1);
}
-ARM_Dynarmic::ARM_Dynarmic(Timing::CoreTiming& core_timing, ExclusiveMonitor& exclusive_monitor,
+ARM_Dynarmic::ARM_Dynarmic(System& system, ExclusiveMonitor& exclusive_monitor,
std::size_t core_index)
- : cb(std::make_unique<ARM_Dynarmic_Callbacks>(*this)), inner_unicorn{core_timing},
- core_index{core_index}, core_timing{core_timing},
- exclusive_monitor{dynamic_cast<DynarmicExclusiveMonitor&>(exclusive_monitor)} {
- ThreadContext ctx{};
- inner_unicorn.SaveContext(ctx);
- PageTableChanged();
- LoadContext(ctx);
-}
+ : cb(std::make_unique<ARM_Dynarmic_Callbacks>(*this)), inner_unicorn{system},
+ core_index{core_index}, system{system},
+ exclusive_monitor{dynamic_cast<DynarmicExclusiveMonitor&>(exclusive_monitor)} {}
ARM_Dynarmic::~ARM_Dynarmic() = default;
@@ -276,9 +268,9 @@ void ARM_Dynarmic::ClearExclusiveState() {
jit->ClearExclusiveState();
}
-void ARM_Dynarmic::PageTableChanged() {
- jit = MakeJit();
- current_page_table = Memory::GetCurrentPageTable();
+void ARM_Dynarmic::PageTableChanged(Common::PageTable& page_table,
+ std::size_t new_address_space_size_in_bits) {
+ jit = MakeJit(page_table, new_address_space_size_in_bits);
}
DynarmicExclusiveMonitor::DynarmicExclusiveMonitor(std::size_t core_count) : monitor(core_count) {}
diff --git a/src/core/arm/dynarmic/arm_dynarmic.h b/src/core/arm/dynarmic/arm_dynarmic.h
index 6cc458296..b701e97a3 100644
--- a/src/core/arm/dynarmic/arm_dynarmic.h
+++ b/src/core/arm/dynarmic/arm_dynarmic.h
@@ -12,24 +12,16 @@
#include "core/arm/exclusive_monitor.h"
#include "core/arm/unicorn/arm_unicorn.h"
-namespace Memory {
-struct PageTable;
-}
-
-namespace Core::Timing {
-class CoreTiming;
-}
-
namespace Core {
class ARM_Dynarmic_Callbacks;
class DynarmicExclusiveMonitor;
+class System;
class ARM_Dynarmic final : public ARM_Interface {
public:
- ARM_Dynarmic(Timing::CoreTiming& core_timing, ExclusiveMonitor& exclusive_monitor,
- std::size_t core_index);
- ~ARM_Dynarmic();
+ ARM_Dynarmic(System& system, ExclusiveMonitor& exclusive_monitor, std::size_t core_index);
+ ~ARM_Dynarmic() override;
void MapBackingMemory(VAddr address, std::size_t size, u8* memory,
Kernel::VMAPermission perms) override;
@@ -56,10 +48,12 @@ public:
void ClearExclusiveState() override;
void ClearInstructionCache() override;
- void PageTableChanged() override;
+ void PageTableChanged(Common::PageTable& new_page_table,
+ std::size_t new_address_space_size_in_bits) override;
private:
- std::unique_ptr<Dynarmic::A64::Jit> MakeJit() const;
+ std::unique_ptr<Dynarmic::A64::Jit> MakeJit(Common::PageTable& page_table,
+ std::size_t address_space_bits) const;
friend class ARM_Dynarmic_Callbacks;
std::unique_ptr<ARM_Dynarmic_Callbacks> cb;
@@ -67,16 +61,14 @@ private:
ARM_Unicorn inner_unicorn;
std::size_t core_index;
- Timing::CoreTiming& core_timing;
+ System& system;
DynarmicExclusiveMonitor& exclusive_monitor;
-
- Memory::PageTable* current_page_table = nullptr;
};
class DynarmicExclusiveMonitor final : public ExclusiveMonitor {
public:
explicit DynarmicExclusiveMonitor(std::size_t core_count);
- ~DynarmicExclusiveMonitor();
+ ~DynarmicExclusiveMonitor() override;
void SetExclusive(std::size_t core_index, VAddr addr) override;
void ClearExclusive() override;
diff --git a/src/core/arm/unicorn/arm_unicorn.cpp b/src/core/arm/unicorn/arm_unicorn.cpp
index a542a098b..4e07fe8b5 100644
--- a/src/core/arm/unicorn/arm_unicorn.cpp
+++ b/src/core/arm/unicorn/arm_unicorn.cpp
@@ -10,7 +10,6 @@
#include "core/core.h"
#include "core/core_timing.h"
#include "core/hle/kernel/svc.h"
-#include "core/memory.h"
namespace Core {
@@ -49,20 +48,6 @@ static void CodeHook(uc_engine* uc, uint64_t address, uint32_t size, void* user_
}
}
-static void InterruptHook(uc_engine* uc, u32 intNo, void* user_data) {
- u32 esr{};
- CHECKED(uc_reg_read(uc, UC_ARM64_REG_ESR, &esr));
-
- auto ec = esr >> 26;
- auto iss = esr & 0xFFFFFF;
-
- switch (ec) {
- case 0x15: // SVC
- Kernel::CallSVC(iss);
- break;
- }
-}
-
static bool UnmappedMemoryHook(uc_engine* uc, uc_mem_type type, u64 addr, int size, u64 value,
void* user_data) {
ARM_Interface::ThreadContext ctx{};
@@ -72,7 +57,7 @@ static bool UnmappedMemoryHook(uc_engine* uc, uc_mem_type type, u64 addr, int si
return {};
}
-ARM_Unicorn::ARM_Unicorn(Timing::CoreTiming& core_timing) : core_timing{core_timing} {
+ARM_Unicorn::ARM_Unicorn(System& system) : system{system} {
CHECKED(uc_open(UC_ARCH_ARM64, UC_MODE_ARM, &uc));
auto fpv = 3 << 20;
@@ -177,7 +162,7 @@ void ARM_Unicorn::Run() {
if (GDBStub::IsServerEnabled()) {
ExecuteInstructions(std::max(4000000, 0));
} else {
- ExecuteInstructions(std::max(core_timing.GetDowncount(), 0));
+ ExecuteInstructions(std::max(system.CoreTiming().GetDowncount(), 0));
}
}
@@ -190,14 +175,15 @@ MICROPROFILE_DEFINE(ARM_Jit_Unicorn, "ARM JIT", "Unicorn", MP_RGB(255, 64, 64));
void ARM_Unicorn::ExecuteInstructions(int num_instructions) {
MICROPROFILE_SCOPE(ARM_Jit_Unicorn);
CHECKED(uc_emu_start(uc, GetPC(), 1ULL << 63, 0, num_instructions));
- core_timing.AddTicks(num_instructions);
+ system.CoreTiming().AddTicks(num_instructions);
if (GDBStub::IsServerEnabled()) {
- if (last_bkpt_hit) {
+ if (last_bkpt_hit && last_bkpt.type == GDBStub::BreakpointType::Execute) {
uc_reg_write(uc, UC_ARM64_REG_PC, &last_bkpt.address);
}
+
Kernel::Thread* thread = Kernel::GetCurrentThread();
SaveContext(thread->GetContext());
- if (last_bkpt_hit || GDBStub::GetCpuStepFlag()) {
+ if (last_bkpt_hit || GDBStub::IsMemoryBreak() || GDBStub::GetCpuStepFlag()) {
last_bkpt_hit = false;
GDBStub::Break();
GDBStub::SendTrap(thread, 5);
@@ -272,4 +258,20 @@ void ARM_Unicorn::RecordBreak(GDBStub::BreakpointAddress 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::CallSVC(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
index dbd6955ea..34e974b4d 100644
--- a/src/core/arm/unicorn/arm_unicorn.h
+++ b/src/core/arm/unicorn/arm_unicorn.h
@@ -9,16 +9,14 @@
#include "core/arm/arm_interface.h"
#include "core/gdbstub/gdbstub.h"
-namespace Core::Timing {
-class CoreTiming;
-}
-
namespace Core {
+class System;
+
class ARM_Unicorn final : public ARM_Interface {
public:
- explicit ARM_Unicorn(Timing::CoreTiming& core_timing);
- ~ARM_Unicorn();
+ explicit ARM_Unicorn(System& system);
+ ~ARM_Unicorn() override;
void MapBackingMemory(VAddr address, std::size_t size, u8* memory,
Kernel::VMAPermission perms) override;
@@ -43,14 +41,16 @@ public:
void Run() override;
void Step() override;
void ClearInstructionCache() override;
- void PageTableChanged() override{};
+ void PageTableChanged(Common::PageTable&, std::size_t) override {}
void RecordBreak(GDBStub::BreakpointAddress bkpt);
private:
+ static void InterruptHook(uc_engine* uc, u32 int_no, void* user_data);
+
uc_engine* uc{};
- Timing::CoreTiming& core_timing;
+ System& system;
GDBStub::BreakpointAddress last_bkpt{};
- bool last_bkpt_hit;
+ bool last_bkpt_hit = false;
};
} // namespace Core
diff --git a/src/core/constants.cpp b/src/core/constants.cpp
new file mode 100644
index 000000000..dccb3e03c
--- /dev/null
+++ b/src/core/constants.cpp
@@ -0,0 +1,17 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/constants.h"
+
+namespace Core::Constants {
+const std::array<u8, 107> ACCOUNT_BACKUP_JPEG{{
+ 0xff, 0xd8, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x02, 0x02,
+ 0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x06, 0x04, 0x04, 0x04, 0x04, 0x04, 0x08, 0x06, 0x06, 0x05,
+ 0x06, 0x09, 0x08, 0x0a, 0x0a, 0x09, 0x08, 0x09, 0x09, 0x0a, 0x0c, 0x0f, 0x0c, 0x0a, 0x0b, 0x0e,
+ 0x0b, 0x09, 0x09, 0x0d, 0x11, 0x0d, 0x0e, 0x0f, 0x10, 0x10, 0x11, 0x10, 0x0a, 0x0c, 0x12, 0x13,
+ 0x12, 0x10, 0x13, 0x0f, 0x10, 0x10, 0x10, 0xff, 0xc9, 0x00, 0x0b, 0x08, 0x00, 0x01, 0x00, 0x01,
+ 0x01, 0x01, 0x11, 0x00, 0xff, 0xcc, 0x00, 0x06, 0x00, 0x10, 0x10, 0x05, 0xff, 0xda, 0x00, 0x08,
+ 0x01, 0x01, 0x00, 0x00, 0x3f, 0x00, 0xd2, 0xcf, 0x20, 0xff, 0xd9,
+}};
+}
diff --git a/src/core/constants.h b/src/core/constants.h
new file mode 100644
index 000000000..6d0ec022a
--- /dev/null
+++ b/src/core/constants.h
@@ -0,0 +1,17 @@
+// 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"
+
+// This is to consolidate system-wide constants that are used by multiple components of yuzu.
+// This is especially to prevent the case of something in frontend duplicating a constexpr array or
+// directly including some service header for the sole purpose of data.
+namespace Core::Constants {
+
+// ACC Service - Blank JPEG used as user icon in absentia of real one.
+extern const std::array<u8, 107> ACCOUNT_BACKUP_JPEG;
+
+} // namespace Core::Constants
diff --git a/src/core/core.cpp b/src/core/core.cpp
index eba2177d1..ff0721079 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -3,9 +3,7 @@
// Refer to the license.txt file included.
#include <array>
-#include <map>
#include <memory>
-#include <thread>
#include <utility>
#include "common/file_util.h"
@@ -17,6 +15,7 @@
#include "core/core_timing.h"
#include "core/cpu_core_manager.h"
#include "core/file_sys/mode.h"
+#include "core/file_sys/registered_cache.h"
#include "core/file_sys/vfs_concat.h"
#include "core/file_sys/vfs_real.h"
#include "core/gdbstub/gdbstub.h"
@@ -25,19 +24,15 @@
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/thread.h"
-#include "core/hle/service/am/applets/software_keyboard.h"
+#include "core/hle/service/am/applets/applets.h"
#include "core/hle/service/service.h"
#include "core/hle/service/sm/sm.h"
#include "core/loader/loader.h"
#include "core/perf_stats.h"
#include "core/settings.h"
#include "core/telemetry_session.h"
-#include "frontend/applets/profile_select.h"
-#include "frontend/applets/software_keyboard.h"
-#include "frontend/applets/web_browser.h"
+#include "file_sys/cheat_engine.h"
#include "video_core/debug_utils/debug_utils.h"
-#include "video_core/gpu_asynch.h"
-#include "video_core/gpu_synch.h"
#include "video_core/renderer_base.h"
#include "video_core/video_core.h"
@@ -79,7 +74,7 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
return vfs->OpenFile(path, FileSys::Mode::Read);
}
struct System::Impl {
- explicit Impl(System& system) : kernel{system} {}
+ explicit Impl(System& system) : kernel{system}, cpu_core_manager{system} {}
Cpu& CurrentCpuCore() {
return cpu_core_manager.GetCurrentCore();
@@ -97,6 +92,7 @@ struct System::Impl {
LOG_DEBUG(HW_Memory, "initialized OK");
core_timing.Initialize();
+ cpu_core_manager.Initialize();
kernel.Initialize();
const auto current_time = std::chrono::duration_cast<std::chrono::seconds>(
@@ -107,17 +103,11 @@ struct System::Impl {
// Create a default fs if one doesn't already exist.
if (virtual_filesystem == nullptr)
virtual_filesystem = std::make_shared<FileSys::RealVfsFilesystem>();
+ if (content_provider == nullptr)
+ content_provider = std::make_unique<FileSys::ContentProviderUnion>();
/// Create default implementations of applets if one is not provided.
- if (profile_selector == nullptr)
- profile_selector = std::make_unique<Core::Frontend::DefaultProfileSelectApplet>();
- if (software_keyboard == nullptr)
- software_keyboard = std::make_unique<Core::Frontend::DefaultSoftwareKeyboardApplet>();
- if (web_browser == nullptr)
- web_browser = std::make_unique<Core::Frontend::DefaultWebBrowserApplet>();
-
- auto main_process = Kernel::Process::Create(kernel, "main");
- kernel.MakeCurrentProcess(main_process.get());
+ applet_manager.SetDefaultAppletsIfMissing();
telemetry_session = std::make_unique<Core::TelemetrySession>();
service_manager = std::make_shared<Service::SM::ServiceManager>();
@@ -130,15 +120,9 @@ struct System::Impl {
return ResultStatus::ErrorVideoCore;
}
- is_powered_on = true;
+ gpu_core = VideoCore::CreateGPU(system);
- if (Settings::values.use_asynchronous_gpu_emulation) {
- gpu_core = std::make_unique<VideoCommon::GPUAsynch>(system, *renderer);
- } else {
- gpu_core = std::make_unique<VideoCommon::GPUSynch>(system, *renderer);
- }
-
- cpu_core_manager.Initialize(system);
+ is_powered_on = true;
LOG_DEBUG(Core, "Initialized OK");
@@ -152,20 +136,10 @@ struct System::Impl {
ResultStatus Load(System& system, Frontend::EmuWindow& emu_window,
const std::string& filepath) {
app_loader = Loader::GetLoader(GetGameFileFromPath(virtual_filesystem, filepath));
-
if (!app_loader) {
LOG_CRITICAL(Core, "Failed to obtain loader for {}!", filepath);
return ResultStatus::ErrorGetLoader;
}
- std::pair<std::optional<u32>, Loader::ResultStatus> system_mode =
- app_loader->LoadKernelSystemMode();
-
- if (system_mode.second != Loader::ResultStatus::Success) {
- LOG_CRITICAL(Core, "Failed to determine system mode (Error {})!",
- static_cast<int>(system_mode.second));
-
- return ResultStatus::ErrorSystemMode;
- }
ResultStatus init_result{Init(system, emu_window)};
if (init_result != ResultStatus::Success) {
@@ -175,7 +149,9 @@ struct System::Impl {
return init_result;
}
- const Loader::ResultStatus load_result{app_loader->Load(*kernel.CurrentProcess())};
+ telemetry_session->AddInitialInfo(*app_loader);
+ auto main_process = Kernel::Process::Create(system, "main");
+ const auto [load_result, load_parameters] = app_loader->Load(*main_process);
if (load_result != Loader::ResultStatus::Success) {
LOG_CRITICAL(Core, "Failed to load ROM (Error {})!", static_cast<int>(load_result));
Shutdown();
@@ -183,6 +159,16 @@ struct System::Impl {
return static_cast<ResultStatus>(static_cast<u32>(ResultStatus::ErrorLoader) +
static_cast<u32>(load_result));
}
+ kernel.MakeCurrentProcess(main_process.get());
+
+ // Main process has been loaded and been made current.
+ // Begin GPU and CPU execution.
+ gpu_core->Start();
+ cpu_core_manager.StartThreads();
+
+ // All threads are started, begin main process execution, now that we're in the clear.
+ main_process->Run(load_parameters->main_thread_priority,
+ load_parameters->main_thread_stack_size);
status = ResultStatus::Success;
return status;
@@ -205,6 +191,7 @@ struct System::Impl {
GDBStub::Shutdown();
Service::Shutdown();
service_manager.reset();
+ cheat_engine.reset();
telemetry_session.reset();
gpu_core.reset();
@@ -219,9 +206,7 @@ struct System::Impl {
app_loader.reset();
// Clear all applets
- profile_selector.reset();
- software_keyboard.reset();
- web_browser.reset();
+ applet_manager.ClearAll();
LOG_DEBUG(Core, "Shutdown OK");
}
@@ -247,6 +232,8 @@ struct System::Impl {
Kernel::KernelCore kernel;
/// RealVfsFilesystem instance
FileSys::VirtualFilesystem virtual_filesystem;
+ /// ContentProviderUnion instance
+ std::unique_ptr<FileSys::ContentProviderUnion> content_provider;
/// AppLoader used to load the current executing application
std::unique_ptr<Loader::AppLoader> app_loader;
std::unique_ptr<VideoCore::RendererBase> renderer;
@@ -255,10 +242,10 @@ struct System::Impl {
CpuCoreManager cpu_core_manager;
bool is_powered_on = false;
+ std::unique_ptr<FileSys::CheatEngine> cheat_engine;
+
/// Frontend applets
- std::unique_ptr<Core::Frontend::ProfileSelectApplet> profile_selector;
- std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet> software_keyboard;
- std::unique_ptr<Core::Frontend::WebBrowserApplet> web_browser;
+ Service::AM::Applets::AppletManager applet_manager;
/// Service manager
std::shared_ptr<Service::SM::ServiceManager> service_manager;
@@ -453,6 +440,13 @@ Tegra::DebugContext* System::GetGPUDebugContext() const {
return impl->debug_context.get();
}
+void System::RegisterCheatList(const std::vector<FileSys::CheatList>& list,
+ const std::string& build_id, VAddr code_region_start,
+ VAddr code_region_end) {
+ impl->cheat_engine = std::make_unique<FileSys::CheatEngine>(*this, list, build_id,
+ code_region_start, code_region_end);
+}
+
void System::SetFilesystem(std::shared_ptr<FileSys::VfsFilesystem> vfs) {
impl->virtual_filesystem = std::move(vfs);
}
@@ -461,32 +455,41 @@ std::shared_ptr<FileSys::VfsFilesystem> System::GetFilesystem() const {
return impl->virtual_filesystem;
}
-void System::SetProfileSelector(std::unique_ptr<Frontend::ProfileSelectApplet> applet) {
- impl->profile_selector = std::move(applet);
+void System::SetAppletFrontendSet(Service::AM::Applets::AppletFrontendSet&& set) {
+ impl->applet_manager.SetAppletFrontendSet(std::move(set));
+}
+
+void System::SetDefaultAppletFrontendSet() {
+ impl->applet_manager.SetDefaultAppletFrontendSet();
+}
+
+Service::AM::Applets::AppletManager& System::GetAppletManager() {
+ return impl->applet_manager;
}
-const Frontend::ProfileSelectApplet& System::GetProfileSelector() const {
- return *impl->profile_selector;
+const Service::AM::Applets::AppletManager& System::GetAppletManager() const {
+ return impl->applet_manager;
}
-void System::SetSoftwareKeyboard(std::unique_ptr<Frontend::SoftwareKeyboardApplet> applet) {
- impl->software_keyboard = std::move(applet);
+void System::SetContentProvider(std::unique_ptr<FileSys::ContentProviderUnion> provider) {
+ impl->content_provider = std::move(provider);
}
-const Frontend::SoftwareKeyboardApplet& System::GetSoftwareKeyboard() const {
- return *impl->software_keyboard;
+FileSys::ContentProvider& System::GetContentProvider() {
+ return *impl->content_provider;
}
-void System::SetWebBrowser(std::unique_ptr<Frontend::WebBrowserApplet> applet) {
- impl->web_browser = std::move(applet);
+const FileSys::ContentProvider& System::GetContentProvider() const {
+ return *impl->content_provider;
}
-Frontend::WebBrowserApplet& System::GetWebBrowser() {
- return *impl->web_browser;
+void System::RegisterContentProvider(FileSys::ContentProviderUnionSlot slot,
+ FileSys::ContentProvider* provider) {
+ impl->content_provider->SetSlot(slot, provider);
}
-const Frontend::WebBrowserApplet& System::GetWebBrowser() const {
- return *impl->web_browser;
+void System::ClearContentProvider(FileSys::ContentProviderUnionSlot slot) {
+ impl->content_provider->ClearSlot(slot);
}
System::ResultStatus System::Init(Frontend::EmuWindow& emu_window) {
diff --git a/src/core/core.h b/src/core/core.h
index ba76a41d8..20959de54 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -14,12 +14,13 @@
namespace Core::Frontend {
class EmuWindow;
-class ProfileSelectApplet;
-class SoftwareKeyboardApplet;
-class WebBrowserApplet;
} // namespace Core::Frontend
namespace FileSys {
+class CheatList;
+class ContentProvider;
+class ContentProviderUnion;
+enum class ContentProviderUnionSlot;
class VfsFilesystem;
} // namespace FileSys
@@ -34,9 +35,18 @@ class AppLoader;
enum class ResultStatus : u16;
} // namespace Loader
-namespace Service::SM {
+namespace Service {
+
+namespace AM::Applets {
+struct AppletFrontendSet;
+class AppletManager;
+} // namespace AM::Applets
+
+namespace SM {
class ServiceManager;
-} // namespace Service::SM
+} // namespace SM
+
+} // namespace Service
namespace Tegra {
class DebugContext;
@@ -88,7 +98,6 @@ public:
Success, ///< Succeeded
ErrorNotInitialized, ///< Error trying to use core prior to initialization
ErrorGetLoader, ///< Error finding the correct application loader
- ErrorSystemMode, ///< Error determining the system mode
ErrorSystemFiles, ///< Error in finding system files
ErrorSharedFont, ///< Error in finding shared font
ErrorVideoCore, ///< Error in the video core
@@ -253,18 +262,27 @@ public:
std::shared_ptr<FileSys::VfsFilesystem> GetFilesystem() const;
- void SetProfileSelector(std::unique_ptr<Frontend::ProfileSelectApplet> applet);
+ void RegisterCheatList(const std::vector<FileSys::CheatList>& list, const std::string& build_id,
+ VAddr code_region_start, VAddr code_region_end);
+
+ void SetAppletFrontendSet(Service::AM::Applets::AppletFrontendSet&& set);
+
+ void SetDefaultAppletFrontendSet();
+
+ Service::AM::Applets::AppletManager& GetAppletManager();
+
+ const Service::AM::Applets::AppletManager& GetAppletManager() const;
- const Frontend::ProfileSelectApplet& GetProfileSelector() const;
+ void SetContentProvider(std::unique_ptr<FileSys::ContentProviderUnion> provider);
- void SetSoftwareKeyboard(std::unique_ptr<Frontend::SoftwareKeyboardApplet> applet);
+ FileSys::ContentProvider& GetContentProvider();
- const Frontend::SoftwareKeyboardApplet& GetSoftwareKeyboard() const;
+ const FileSys::ContentProvider& GetContentProvider() const;
- void SetWebBrowser(std::unique_ptr<Frontend::WebBrowserApplet> applet);
+ void RegisterContentProvider(FileSys::ContentProviderUnionSlot slot,
+ FileSys::ContentProvider* provider);
- Frontend::WebBrowserApplet& GetWebBrowser();
- const Frontend::WebBrowserApplet& GetWebBrowser() const;
+ void ClearContentProvider(FileSys::ContentProviderUnionSlot slot);
private:
System();
diff --git a/src/core/core_cpu.cpp b/src/core/core_cpu.cpp
index 54aa21a3a..ba63c3e61 100644
--- a/src/core/core_cpu.cpp
+++ b/src/core/core_cpu.cpp
@@ -11,6 +11,7 @@
#endif
#include "core/arm/exclusive_monitor.h"
#include "core/arm/unicorn/arm_unicorn.h"
+#include "core/core.h"
#include "core/core_cpu.h"
#include "core/core_timing.h"
#include "core/hle/kernel/scheduler.h"
@@ -21,7 +22,7 @@
namespace Core {
void CpuBarrier::NotifyEnd() {
- std::unique_lock<std::mutex> lock(mutex);
+ std::unique_lock lock{mutex};
end = true;
condition.notify_all();
}
@@ -33,7 +34,7 @@ bool CpuBarrier::Rendezvous() {
}
if (!end) {
- std::unique_lock<std::mutex> lock(mutex);
+ std::unique_lock lock{mutex};
--cores_waiting;
if (!cores_waiting) {
@@ -49,21 +50,21 @@ bool CpuBarrier::Rendezvous() {
return false;
}
-Cpu::Cpu(Timing::CoreTiming& core_timing, ExclusiveMonitor& exclusive_monitor,
- CpuBarrier& cpu_barrier, std::size_t core_index)
- : cpu_barrier{cpu_barrier}, core_timing{core_timing}, core_index{core_index} {
+Cpu::Cpu(System& system, ExclusiveMonitor& exclusive_monitor, CpuBarrier& cpu_barrier,
+ std::size_t core_index)
+ : cpu_barrier{cpu_barrier}, core_timing{system.CoreTiming()}, core_index{core_index} {
if (Settings::values.use_cpu_jit) {
#ifdef ARCHITECTURE_x86_64
- arm_interface = std::make_unique<ARM_Dynarmic>(core_timing, exclusive_monitor, core_index);
+ arm_interface = std::make_unique<ARM_Dynarmic>(system, exclusive_monitor, core_index);
#else
- arm_interface = std::make_unique<ARM_Unicorn>();
+ arm_interface = std::make_unique<ARM_Unicorn>(system);
LOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available");
#endif
} else {
- arm_interface = std::make_unique<ARM_Unicorn>(core_timing);
+ arm_interface = std::make_unique<ARM_Unicorn>(system);
}
- scheduler = std::make_unique<Kernel::Scheduler>(*arm_interface);
+ scheduler = std::make_unique<Kernel::Scheduler>(system, *arm_interface);
}
Cpu::~Cpu() = default;
@@ -130,7 +131,7 @@ void Cpu::Reschedule() {
reschedule_pending = false;
// Lock the global kernel mutex when we manipulate the HLE state
- std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
+ std::lock_guard lock{HLE::g_hle_lock};
scheduler->Reschedule();
}
diff --git a/src/core/core_cpu.h b/src/core/core_cpu.h
index e2204c6b0..7589beb8c 100644
--- a/src/core/core_cpu.h
+++ b/src/core/core_cpu.h
@@ -15,6 +15,10 @@ namespace Kernel {
class Scheduler;
}
+namespace Core {
+class System;
+}
+
namespace Core::Timing {
class CoreTiming;
}
@@ -45,8 +49,8 @@ private:
class Cpu {
public:
- Cpu(Timing::CoreTiming& core_timing, ExclusiveMonitor& exclusive_monitor,
- CpuBarrier& cpu_barrier, std::size_t core_index);
+ Cpu(System& system, ExclusiveMonitor& exclusive_monitor, CpuBarrier& cpu_barrier,
+ std::size_t core_index);
~Cpu();
void RunLoop(bool tight_loop = true);
diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp
index a0dd5db24..41adb2302 100644
--- a/src/core/core_timing.cpp
+++ b/src/core/core_timing.cpp
@@ -186,7 +186,7 @@ void CoreTiming::Advance() {
Event evt = std::move(event_queue.front());
std::pop_heap(event_queue.begin(), event_queue.end(), std::greater<>());
event_queue.pop_back();
- evt.type->callback(evt.userdata, static_cast<int>(global_timer - evt.time));
+ evt.type->callback(evt.userdata, global_timer - evt.time);
}
is_global_timer_sane = false;
diff --git a/src/core/core_timing.h b/src/core/core_timing.h
index 59163bae1..9d2efde37 100644
--- a/src/core/core_timing.h
+++ b/src/core/core_timing.h
@@ -15,7 +15,7 @@
namespace Core::Timing {
/// A callback that may be scheduled for a particular core timing event.
-using TimedCallback = std::function<void(u64 userdata, int cycles_late)>;
+using TimedCallback = std::function<void(u64 userdata, s64 cycles_late)>;
/// Contains the characteristics of a particular event.
struct EventType {
diff --git a/src/core/core_timing_util.cpp b/src/core/core_timing_util.cpp
index 88ff70233..a10472a95 100644
--- a/src/core/core_timing_util.cpp
+++ b/src/core/core_timing_util.cpp
@@ -7,57 +7,51 @@
#include <cinttypes>
#include <limits>
#include "common/logging/log.h"
+#include "common/uint128.h"
namespace Core::Timing {
constexpr u64 MAX_VALUE_TO_MULTIPLY = std::numeric_limits<s64>::max() / BASE_CLOCK_RATE;
-s64 usToCycles(s64 us) {
- if (us / 1000000 > MAX_VALUE_TO_MULTIPLY) {
+s64 msToCycles(std::chrono::milliseconds ms) {
+ if (static_cast<u64>(ms.count() / 1000) > MAX_VALUE_TO_MULTIPLY) {
LOG_ERROR(Core_Timing, "Integer overflow, use max value");
return std::numeric_limits<s64>::max();
}
- if (us > MAX_VALUE_TO_MULTIPLY) {
+ if (static_cast<u64>(ms.count()) > MAX_VALUE_TO_MULTIPLY) {
LOG_DEBUG(Core_Timing, "Time very big, do rounding");
- return BASE_CLOCK_RATE * (us / 1000000);
+ return BASE_CLOCK_RATE * (ms.count() / 1000);
}
- return (BASE_CLOCK_RATE * us) / 1000000;
+ return (BASE_CLOCK_RATE * ms.count()) / 1000;
}
-s64 usToCycles(u64 us) {
- if (us / 1000000 > MAX_VALUE_TO_MULTIPLY) {
+s64 usToCycles(std::chrono::microseconds us) {
+ if (static_cast<u64>(us.count() / 1000000) > MAX_VALUE_TO_MULTIPLY) {
LOG_ERROR(Core_Timing, "Integer overflow, use max value");
return std::numeric_limits<s64>::max();
}
- if (us > MAX_VALUE_TO_MULTIPLY) {
+ if (static_cast<u64>(us.count()) > MAX_VALUE_TO_MULTIPLY) {
LOG_DEBUG(Core_Timing, "Time very big, do rounding");
- return BASE_CLOCK_RATE * static_cast<s64>(us / 1000000);
+ return BASE_CLOCK_RATE * (us.count() / 1000000);
}
- return (BASE_CLOCK_RATE * static_cast<s64>(us)) / 1000000;
+ return (BASE_CLOCK_RATE * us.count()) / 1000000;
}
-s64 nsToCycles(s64 ns) {
- if (ns / 1000000000 > MAX_VALUE_TO_MULTIPLY) {
+s64 nsToCycles(std::chrono::nanoseconds ns) {
+ if (static_cast<u64>(ns.count() / 1000000000) > MAX_VALUE_TO_MULTIPLY) {
LOG_ERROR(Core_Timing, "Integer overflow, use max value");
return std::numeric_limits<s64>::max();
}
- if (ns > MAX_VALUE_TO_MULTIPLY) {
+ if (static_cast<u64>(ns.count()) > MAX_VALUE_TO_MULTIPLY) {
LOG_DEBUG(Core_Timing, "Time very big, do rounding");
- return BASE_CLOCK_RATE * (ns / 1000000000);
+ return BASE_CLOCK_RATE * (ns.count() / 1000000000);
}
- return (BASE_CLOCK_RATE * ns) / 1000000000;
+ return (BASE_CLOCK_RATE * ns.count()) / 1000000000;
}
-s64 nsToCycles(u64 ns) {
- if (ns / 1000000000 > MAX_VALUE_TO_MULTIPLY) {
- LOG_ERROR(Core_Timing, "Integer overflow, use max value");
- return std::numeric_limits<s64>::max();
- }
- if (ns > MAX_VALUE_TO_MULTIPLY) {
- LOG_DEBUG(Core_Timing, "Time very big, do rounding");
- return BASE_CLOCK_RATE * (static_cast<s64>(ns) / 1000000000);
- }
- return (BASE_CLOCK_RATE * static_cast<s64>(ns)) / 1000000000;
+u64 CpuCyclesToClockCycles(u64 ticks) {
+ const u128 temporal = Common::Multiply64Into128(ticks, CNTFREQ);
+ return Common::Divide128On32(temporal, static_cast<u32>(BASE_CLOCK_RATE)).first;
}
} // namespace Core::Timing
diff --git a/src/core/core_timing_util.h b/src/core/core_timing_util.h
index 513cfac1b..cdd84d70f 100644
--- a/src/core/core_timing_util.h
+++ b/src/core/core_timing_util.h
@@ -4,6 +4,7 @@
#pragma once
+#include <chrono>
#include "common/common_types.h"
namespace Core::Timing {
@@ -11,54 +12,24 @@ namespace Core::Timing {
// The below clock rate is based on Switch's clockspeed being widely known as 1.020GHz
// The exact value used is of course unverified.
constexpr u64 BASE_CLOCK_RATE = 1019215872; // Switch clock speed is 1020MHz un/docked
+constexpr u64 CNTFREQ = 19200000; // Value from fusee.
-inline s64 msToCycles(int ms) {
- // since ms is int there is no way to overflow
- return BASE_CLOCK_RATE * static_cast<s64>(ms) / 1000;
-}
-
-inline s64 msToCycles(float ms) {
- return static_cast<s64>(BASE_CLOCK_RATE * (0.001f) * ms);
-}
-
-inline s64 msToCycles(double ms) {
- return static_cast<s64>(BASE_CLOCK_RATE * (0.001) * ms);
-}
-
-inline s64 usToCycles(float us) {
- return static_cast<s64>(BASE_CLOCK_RATE * (0.000001f) * us);
-}
-
-inline s64 usToCycles(int us) {
- return (BASE_CLOCK_RATE * static_cast<s64>(us) / 1000000);
-}
-
-s64 usToCycles(s64 us);
-
-s64 usToCycles(u64 us);
-
-inline s64 nsToCycles(float ns) {
- return static_cast<s64>(BASE_CLOCK_RATE * (0.000000001f) * ns);
-}
+s64 msToCycles(std::chrono::milliseconds ms);
+s64 usToCycles(std::chrono::microseconds us);
+s64 nsToCycles(std::chrono::nanoseconds ns);
-inline s64 nsToCycles(int ns) {
- return BASE_CLOCK_RATE * static_cast<s64>(ns) / 1000000000;
+inline std::chrono::milliseconds CyclesToMs(s64 cycles) {
+ return std::chrono::milliseconds(cycles * 1000 / BASE_CLOCK_RATE);
}
-s64 nsToCycles(s64 ns);
-
-s64 nsToCycles(u64 ns);
-
-inline u64 cyclesToNs(s64 cycles) {
- return cycles * 1000000000 / BASE_CLOCK_RATE;
+inline std::chrono::nanoseconds CyclesToNs(s64 cycles) {
+ return std::chrono::nanoseconds(cycles * 1000000000 / BASE_CLOCK_RATE);
}
-inline s64 cyclesToUs(s64 cycles) {
- return cycles * 1000000 / BASE_CLOCK_RATE;
+inline std::chrono::microseconds CyclesToUs(s64 cycles) {
+ return std::chrono::microseconds(cycles * 1000000 / BASE_CLOCK_RATE);
}
-inline u64 cyclesToMs(s64 cycles) {
- return cycles * 1000 / BASE_CLOCK_RATE;
-}
+u64 CpuCyclesToClockCycles(u64 ticks);
} // namespace Core::Timing
diff --git a/src/core/cpu_core_manager.cpp b/src/core/cpu_core_manager.cpp
index 2ddb3610d..8fcb4eeb1 100644
--- a/src/core/cpu_core_manager.cpp
+++ b/src/core/cpu_core_manager.cpp
@@ -19,18 +19,19 @@ void RunCpuCore(const System& system, Cpu& cpu_state) {
}
} // Anonymous namespace
-CpuCoreManager::CpuCoreManager() = default;
+CpuCoreManager::CpuCoreManager(System& system) : system{system} {}
CpuCoreManager::~CpuCoreManager() = default;
-void CpuCoreManager::Initialize(System& system) {
+void CpuCoreManager::Initialize() {
barrier = std::make_unique<CpuBarrier>();
exclusive_monitor = Cpu::MakeExclusiveMonitor(cores.size());
for (std::size_t index = 0; index < cores.size(); ++index) {
- cores[index] =
- std::make_unique<Cpu>(system.CoreTiming(), *exclusive_monitor, *barrier, index);
+ cores[index] = std::make_unique<Cpu>(system, *exclusive_monitor, *barrier, index);
}
+}
+void CpuCoreManager::StartThreads() {
// Create threads for CPU cores 1-3, and build thread_to_cpu map
// CPU core 0 is run on the main thread
thread_to_cpu[std::this_thread::get_id()] = cores[0].get();
diff --git a/src/core/cpu_core_manager.h b/src/core/cpu_core_manager.h
index a4d70ec56..2cbbf8216 100644
--- a/src/core/cpu_core_manager.h
+++ b/src/core/cpu_core_manager.h
@@ -18,7 +18,7 @@ class System;
class CpuCoreManager {
public:
- CpuCoreManager();
+ explicit CpuCoreManager(System& system);
CpuCoreManager(const CpuCoreManager&) = delete;
CpuCoreManager(CpuCoreManager&&) = delete;
@@ -27,7 +27,8 @@ public:
CpuCoreManager& operator=(const CpuCoreManager&) = delete;
CpuCoreManager& operator=(CpuCoreManager&&) = delete;
- void Initialize(System& system);
+ void Initialize();
+ void StartThreads();
void Shutdown();
Cpu& GetCore(std::size_t index);
@@ -54,6 +55,8 @@ private:
/// Map of guest threads to CPU cores
std::map<std::thread::id, Cpu*> thread_to_cpu;
+
+ System& system;
};
} // namespace Core
diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp
index dfac9a4b3..6dd633363 100644
--- a/src/core/crypto/key_manager.cpp
+++ b/src/core/crypto/key_manager.cpp
@@ -22,6 +22,7 @@
#include "common/file_util.h"
#include "common/hex_util.h"
#include "common/logging/log.h"
+#include "core/core.h"
#include "core/crypto/aes_util.h"
#include "core/crypto/key_manager.h"
#include "core/crypto/partition_data_manager.h"
@@ -571,7 +572,7 @@ void KeyManager::WriteKeyToFile(KeyCategory category, std::string_view keyname,
<< "# If you are experiencing issues involving keys, it may help to delete this file\n";
}
- file << fmt::format("\n{} = {}", keyname, Common::HexArrayToString(key));
+ file << fmt::format("\n{} = {}", keyname, Common::HexToString(key));
AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, filename, category == KeyCategory::Title);
}
@@ -582,7 +583,7 @@ void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) {
Key128 rights_id;
std::memcpy(rights_id.data(), &field2, sizeof(u64));
std::memcpy(rights_id.data() + sizeof(u64), &field1, sizeof(u64));
- WriteKeyToFile(KeyCategory::Title, Common::HexArrayToString(rights_id), key);
+ WriteKeyToFile(KeyCategory::Title, Common::HexToString(rights_id), key);
}
auto category = KeyCategory::Standard;
@@ -794,7 +795,7 @@ void KeyManager::DeriveBase() {
void KeyManager::DeriveETicket(PartitionDataManager& data) {
// ETicket keys
- const auto es = Service::FileSystem::GetUnionContents().GetEntry(
+ const auto es = Core::System::GetInstance().GetContentProvider().GetEntry(
0x0100000000000033, FileSys::ContentRecordType::Program);
if (es == nullptr)
diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp
index 2c145bd09..626ed0042 100644
--- a/src/core/file_sys/card_image.cpp
+++ b/src/core/file_sys/card_image.cpp
@@ -18,11 +18,16 @@
namespace FileSys {
-constexpr std::array<const char*, 0x4> partition_names = {"update", "normal", "secure", "logo"};
+constexpr std::array partition_names{
+ "update",
+ "normal",
+ "secure",
+ "logo",
+};
XCI::XCI(VirtualFile file_)
: file(std::move(file_)), program_nca_status{Loader::ResultStatus::ErrorXCIMissingProgramNCA},
- partitions(0x4) {
+ partitions(partition_names.size()) {
if (file->ReadObject(&header) != sizeof(GamecardHeader)) {
status = Loader::ResultStatus::ErrorBadXCIHeader;
return;
@@ -43,23 +48,24 @@ XCI::XCI(VirtualFile file_)
for (XCIPartition partition :
{XCIPartition::Update, XCIPartition::Normal, XCIPartition::Secure, XCIPartition::Logo}) {
- auto raw = main_hfs.GetFile(partition_names[static_cast<std::size_t>(partition)]);
- if (raw != nullptr)
- partitions[static_cast<std::size_t>(partition)] =
- std::make_shared<PartitionFilesystem>(raw);
+ const auto partition_idx = static_cast<std::size_t>(partition);
+ auto raw = main_hfs.GetFile(partition_names[partition_idx]);
+
+ if (raw != nullptr) {
+ partitions[partition_idx] = std::make_shared<PartitionFilesystem>(std::move(raw));
+ }
}
secure_partition = std::make_shared<NSP>(
main_hfs.GetFile(partition_names[static_cast<std::size_t>(XCIPartition::Secure)]));
- const auto secure_ncas = secure_partition->GetNCAsCollapsed();
- std::copy(secure_ncas.begin(), secure_ncas.end(), std::back_inserter(ncas));
-
+ ncas = secure_partition->GetNCAsCollapsed();
program =
secure_partition->GetNCA(secure_partition->GetProgramTitleID(), ContentRecordType::Program);
program_nca_status = secure_partition->GetProgramStatus(secure_partition->GetProgramTitleID());
- if (program_nca_status == Loader::ResultStatus::ErrorNSPMissingProgramNCA)
+ if (program_nca_status == Loader::ResultStatus::ErrorNSPMissingProgramNCA) {
program_nca_status = Loader::ResultStatus::ErrorXCIMissingProgramNCA;
+ }
auto result = AddNCAFromPartition(XCIPartition::Update);
if (result != Loader::ResultStatus::Success) {
@@ -147,8 +153,9 @@ std::shared_ptr<NCA> XCI::GetNCAByType(NCAContentType type) const {
VirtualFile XCI::GetNCAFileByType(NCAContentType type) const {
auto nca = GetNCAByType(type);
- if (nca != nullptr)
+ if (nca != nullptr) {
return nca->GetBaseFile();
+ }
return nullptr;
}
@@ -169,17 +176,22 @@ VirtualDir XCI::GetParentDirectory() const {
}
Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) {
- if (partitions[static_cast<std::size_t>(part)] == nullptr) {
+ const auto partition_index = static_cast<std::size_t>(part);
+ const auto& partition = partitions[partition_index];
+
+ if (partition == nullptr) {
return Loader::ResultStatus::ErrorXCIMissingPartition;
}
- for (const VirtualFile& file : partitions[static_cast<std::size_t>(part)]->GetFiles()) {
- if (file->GetExtension() != "nca")
+ for (const VirtualFile& file : partition->GetFiles()) {
+ if (file->GetExtension() != "nca") {
continue;
+ }
+
auto nca = std::make_shared<NCA>(file, nullptr, 0, keys);
- // TODO(DarkLordZach): Add proper Rev1+ Support
- if (nca->IsUpdate())
+ if (nca->IsUpdate()) {
continue;
+ }
if (nca->GetType() == NCAContentType::Program) {
program_nca_status = nca->GetStatus();
}
@@ -188,7 +200,7 @@ Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) {
} else {
const u16 error_id = static_cast<u16>(nca->GetStatus());
LOG_CRITICAL(Loader, "Could not load NCA {}/{}, failed with error code {:04X} ({})",
- partition_names[static_cast<std::size_t>(part)], nca->GetName(), error_id,
+ partition_names[partition_index], nca->GetName(), error_id,
nca->GetStatus());
}
}
diff --git a/src/core/file_sys/cheat_engine.cpp b/src/core/file_sys/cheat_engine.cpp
new file mode 100644
index 000000000..b06c2f20a
--- /dev/null
+++ b/src/core/file_sys/cheat_engine.cpp
@@ -0,0 +1,492 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <locale>
+#include "common/hex_util.h"
+#include "common/microprofile.h"
+#include "common/swap.h"
+#include "core/core.h"
+#include "core/core_timing.h"
+#include "core/core_timing_util.h"
+#include "core/file_sys/cheat_engine.h"
+#include "core/hle/kernel/process.h"
+#include "core/hle/service/hid/controllers/npad.h"
+#include "core/hle/service/hid/hid.h"
+#include "core/hle/service/sm/sm.h"
+
+namespace FileSys {
+
+constexpr s64 CHEAT_ENGINE_TICKS = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 60);
+constexpr u32 KEYPAD_BITMASK = 0x3FFFFFF;
+
+u64 Cheat::Address() const {
+ u64 out;
+ std::memcpy(&out, raw.data(), sizeof(u64));
+ return Common::swap64(out) & 0xFFFFFFFFFF;
+}
+
+u64 Cheat::ValueWidth(u64 offset) const {
+ return Value(offset, width);
+}
+
+u64 Cheat::Value(u64 offset, u64 width) const {
+ u64 out;
+ std::memcpy(&out, raw.data() + offset, sizeof(u64));
+ out = Common::swap64(out);
+ if (width == 8)
+ return out;
+ return out & ((1ull << (width * CHAR_BIT)) - 1);
+}
+
+u32 Cheat::KeypadValue() const {
+ u32 out;
+ std::memcpy(&out, raw.data(), sizeof(u32));
+ return Common::swap32(out) & 0x0FFFFFFF;
+}
+
+void CheatList::SetMemoryParameters(VAddr main_begin, VAddr heap_begin, VAddr main_end,
+ VAddr heap_end, MemoryWriter writer, MemoryReader reader) {
+ this->main_region_begin = main_begin;
+ this->main_region_end = main_end;
+ this->heap_region_begin = heap_begin;
+ this->heap_region_end = heap_end;
+ this->writer = writer;
+ this->reader = reader;
+}
+
+MICROPROFILE_DEFINE(Cheat_Engine, "Add-Ons", "Cheat Engine", MP_RGB(70, 200, 70));
+
+void CheatList::Execute() {
+ MICROPROFILE_SCOPE(Cheat_Engine);
+
+ std::fill(scratch.begin(), scratch.end(), 0);
+ in_standard = false;
+ for (std::size_t i = 0; i < master_list.size(); ++i) {
+ LOG_DEBUG(Common_Filesystem, "Executing block #{:08X} ({})", i, master_list[i].first);
+ current_block = i;
+ ExecuteBlock(master_list[i].second);
+ }
+
+ in_standard = true;
+ for (std::size_t i = 0; i < standard_list.size(); ++i) {
+ LOG_DEBUG(Common_Filesystem, "Executing block #{:08X} ({})", i, standard_list[i].first);
+ current_block = i;
+ ExecuteBlock(standard_list[i].second);
+ }
+}
+
+CheatList::CheatList(const Core::System& system_, ProgramSegment master, ProgramSegment standard)
+ : master_list{std::move(master)}, standard_list{std::move(standard)}, system{&system_} {}
+
+bool CheatList::EvaluateConditional(const Cheat& cheat) const {
+ using ComparisonFunction = bool (*)(u64, u64);
+ constexpr std::array<ComparisonFunction, 6> comparison_functions{
+ [](u64 a, u64 b) { return a > b; }, [](u64 a, u64 b) { return a >= b; },
+ [](u64 a, u64 b) { return a < b; }, [](u64 a, u64 b) { return a <= b; },
+ [](u64 a, u64 b) { return a == b; }, [](u64 a, u64 b) { return a != b; },
+ };
+
+ if (cheat.type == CodeType::ConditionalInput) {
+ const auto applet_resource =
+ system->ServiceManager().GetService<Service::HID::Hid>("hid")->GetAppletResource();
+ if (applet_resource == nullptr) {
+ LOG_WARNING(
+ Common_Filesystem,
+ "Attempted to evaluate input conditional, but applet resource is not initialized!");
+ return false;
+ }
+
+ const auto press_state =
+ applet_resource
+ ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad)
+ .GetAndResetPressState();
+ return ((press_state & cheat.KeypadValue()) & KEYPAD_BITMASK) != 0;
+ }
+
+ ASSERT(cheat.type == CodeType::Conditional);
+
+ const auto offset =
+ cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin;
+ ASSERT(static_cast<u8>(cheat.comparison_op.Value()) < 6);
+ auto* function = comparison_functions[static_cast<u8>(cheat.comparison_op.Value())];
+ const auto addr = cheat.Address() + offset;
+
+ return function(reader(cheat.width, SanitizeAddress(addr)), cheat.ValueWidth(8));
+}
+
+void CheatList::ProcessBlockPairs(const Block& block) {
+ block_pairs.clear();
+
+ u64 scope = 0;
+ std::map<u64, u64> pairs;
+
+ for (std::size_t i = 0; i < block.size(); ++i) {
+ const auto& cheat = block[i];
+
+ switch (cheat.type) {
+ case CodeType::Conditional:
+ case CodeType::ConditionalInput:
+ pairs.insert_or_assign(scope, i);
+ ++scope;
+ break;
+ case CodeType::EndConditional: {
+ --scope;
+ const auto idx = pairs.at(scope);
+ block_pairs.insert_or_assign(idx, i);
+ break;
+ }
+ case CodeType::Loop: {
+ if (cheat.end_of_loop) {
+ --scope;
+ const auto idx = pairs.at(scope);
+ block_pairs.insert_or_assign(idx, i);
+ } else {
+ pairs.insert_or_assign(scope, i);
+ ++scope;
+ }
+ break;
+ }
+ }
+ }
+}
+
+void CheatList::WriteImmediate(const Cheat& cheat) {
+ const auto offset =
+ cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin;
+ const auto& register_3 = scratch.at(cheat.register_3);
+
+ const auto addr = cheat.Address() + offset + register_3;
+ LOG_DEBUG(Common_Filesystem, "writing value={:016X} to addr={:016X}", addr,
+ cheat.Value(8, cheat.width));
+ writer(cheat.width, SanitizeAddress(addr), cheat.ValueWidth(8));
+}
+
+void CheatList::BeginConditional(const Cheat& cheat) {
+ if (EvaluateConditional(cheat)) {
+ return;
+ }
+
+ const auto iter = block_pairs.find(current_index);
+ ASSERT(iter != block_pairs.end());
+ current_index = iter->second - 1;
+}
+
+void CheatList::EndConditional(const Cheat& cheat) {
+ LOG_DEBUG(Common_Filesystem, "Ending conditional block.");
+}
+
+void CheatList::Loop(const Cheat& cheat) {
+ if (cheat.end_of_loop.Value())
+ ASSERT(!cheat.end_of_loop.Value());
+
+ auto& register_3 = scratch.at(cheat.register_3);
+ const auto iter = block_pairs.find(current_index);
+ ASSERT(iter != block_pairs.end());
+ ASSERT(iter->first < iter->second);
+
+ const s32 initial_value = static_cast<s32>(cheat.Value(4, sizeof(s32)));
+ for (s32 i = initial_value; i >= 0; --i) {
+ register_3 = static_cast<u64>(i);
+ for (std::size_t c = iter->first + 1; c < iter->second; ++c) {
+ current_index = c;
+ ExecuteSingleCheat(
+ (in_standard ? standard_list : master_list)[current_block].second[c]);
+ }
+ }
+
+ current_index = iter->second;
+}
+
+void CheatList::LoadImmediate(const Cheat& cheat) {
+ auto& register_3 = scratch.at(cheat.register_3);
+
+ LOG_DEBUG(Common_Filesystem, "setting register={:01X} equal to value={:016X}", cheat.register_3,
+ cheat.Value(4, 8));
+ register_3 = cheat.Value(4, 8);
+}
+
+void CheatList::LoadIndexed(const Cheat& cheat) {
+ const auto offset =
+ cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin;
+ auto& register_3 = scratch.at(cheat.register_3);
+
+ const auto addr = (cheat.load_from_register.Value() ? register_3 : offset) + cheat.Address();
+ LOG_DEBUG(Common_Filesystem, "writing indexed value to register={:01X}, addr={:016X}",
+ cheat.register_3, addr);
+ register_3 = reader(cheat.width, SanitizeAddress(addr));
+}
+
+void CheatList::StoreIndexed(const Cheat& cheat) {
+ const auto& register_3 = scratch.at(cheat.register_3);
+
+ const auto addr =
+ register_3 + (cheat.add_additional_register.Value() ? scratch.at(cheat.register_6) : 0);
+ LOG_DEBUG(Common_Filesystem, "writing value={:016X} to addr={:016X}",
+ cheat.Value(4, cheat.width), addr);
+ writer(cheat.width, SanitizeAddress(addr), cheat.ValueWidth(4));
+}
+
+void CheatList::RegisterArithmetic(const Cheat& cheat) {
+ using ArithmeticFunction = u64 (*)(u64, u64);
+ constexpr std::array<ArithmeticFunction, 5> arithmetic_functions{
+ [](u64 a, u64 b) { return a + b; }, [](u64 a, u64 b) { return a - b; },
+ [](u64 a, u64 b) { return a * b; }, [](u64 a, u64 b) { return a << b; },
+ [](u64 a, u64 b) { return a >> b; },
+ };
+
+ using ArithmeticOverflowCheck = bool (*)(u64, u64);
+ constexpr std::array<ArithmeticOverflowCheck, 5> arithmetic_overflow_checks{
+ [](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() - b); }, // a + b
+ [](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() + b); }, // a - b
+ [](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() / b); }, // a * b
+ [](u64 a, u64 b) { return b >= 64 || (a & ~((1ull << (64 - b)) - 1)) != 0; }, // a << b
+ [](u64 a, u64 b) { return b >= 64 || (a & ((1ull << b) - 1)) != 0; }, // a >> b
+ };
+
+ static_assert(sizeof(arithmetic_functions) == sizeof(arithmetic_overflow_checks),
+ "Missing or have extra arithmetic overflow checks compared to functions!");
+
+ auto& register_3 = scratch.at(cheat.register_3);
+
+ ASSERT(static_cast<u8>(cheat.arithmetic_op.Value()) < 5);
+ auto* function = arithmetic_functions[static_cast<u8>(cheat.arithmetic_op.Value())];
+ auto* overflow_function =
+ arithmetic_overflow_checks[static_cast<u8>(cheat.arithmetic_op.Value())];
+ LOG_DEBUG(Common_Filesystem, "performing arithmetic with register={:01X}, value={:016X}",
+ cheat.register_3, cheat.ValueWidth(4));
+
+ if (overflow_function(register_3, cheat.ValueWidth(4))) {
+ LOG_WARNING(Common_Filesystem,
+ "overflow will occur when performing arithmetic operation={:02X} with operands "
+ "a={:016X}, b={:016X}!",
+ static_cast<u8>(cheat.arithmetic_op.Value()), register_3, cheat.ValueWidth(4));
+ }
+
+ register_3 = function(register_3, cheat.ValueWidth(4));
+}
+
+void CheatList::BeginConditionalInput(const Cheat& cheat) {
+ if (EvaluateConditional(cheat))
+ return;
+
+ const auto iter = block_pairs.find(current_index);
+ ASSERT(iter != block_pairs.end());
+ current_index = iter->second - 1;
+}
+
+VAddr CheatList::SanitizeAddress(VAddr in) const {
+ if ((in < main_region_begin || in >= main_region_end) &&
+ (in < heap_region_begin || in >= heap_region_end)) {
+ LOG_ERROR(Common_Filesystem,
+ "Cheat attempting to access memory at invalid address={:016X}, if this persists, "
+ "the cheat may be incorrect. However, this may be normal early in execution if "
+ "the game has not properly set up yet.",
+ in);
+ return 0; ///< Invalid addresses will hard crash
+ }
+
+ return in;
+}
+
+void CheatList::ExecuteSingleCheat(const Cheat& cheat) {
+ using CheatOperationFunction = void (CheatList::*)(const Cheat&);
+ constexpr std::array<CheatOperationFunction, 9> cheat_operation_functions{
+ &CheatList::WriteImmediate, &CheatList::BeginConditional,
+ &CheatList::EndConditional, &CheatList::Loop,
+ &CheatList::LoadImmediate, &CheatList::LoadIndexed,
+ &CheatList::StoreIndexed, &CheatList::RegisterArithmetic,
+ &CheatList::BeginConditionalInput,
+ };
+
+ const auto index = static_cast<u8>(cheat.type.Value());
+ ASSERT(index < sizeof(cheat_operation_functions));
+ const auto op = cheat_operation_functions[index];
+ (this->*op)(cheat);
+}
+
+void CheatList::ExecuteBlock(const Block& block) {
+ encountered_loops.clear();
+
+ ProcessBlockPairs(block);
+ for (std::size_t i = 0; i < block.size(); ++i) {
+ current_index = i;
+ ExecuteSingleCheat(block[i]);
+ i = current_index;
+ }
+}
+
+CheatParser::~CheatParser() = default;
+
+CheatList CheatParser::MakeCheatList(const Core::System& system, CheatList::ProgramSegment master,
+ CheatList::ProgramSegment standard) const {
+ return {system, std::move(master), std::move(standard)};
+}
+
+TextCheatParser::~TextCheatParser() = default;
+
+CheatList TextCheatParser::Parse(const Core::System& system, const std::vector<u8>& data) const {
+ std::stringstream ss;
+ ss.write(reinterpret_cast<const char*>(data.data()), data.size());
+
+ std::vector<std::string> lines;
+ std::string stream_line;
+ while (std::getline(ss, stream_line)) {
+ // Remove a trailing \r
+ if (!stream_line.empty() && stream_line.back() == '\r')
+ stream_line.pop_back();
+ lines.push_back(std::move(stream_line));
+ }
+
+ CheatList::ProgramSegment master_list;
+ CheatList::ProgramSegment standard_list;
+
+ for (std::size_t i = 0; i < lines.size(); ++i) {
+ auto line = lines[i];
+
+ if (!line.empty() && (line[0] == '[' || line[0] == '{')) {
+ const auto master = line[0] == '{';
+ const auto begin = master ? line.find('{') : line.find('[');
+ const auto end = master ? line.rfind('}') : line.rfind(']');
+
+ ASSERT(begin != std::string::npos && end != std::string::npos);
+
+ const std::string patch_name{line.begin() + begin + 1, line.begin() + end};
+ CheatList::Block block{};
+
+ while (i < lines.size() - 1) {
+ line = lines[++i];
+ if (!line.empty() && (line[0] == '[' || line[0] == '{')) {
+ --i;
+ break;
+ }
+
+ if (line.size() < 8)
+ continue;
+
+ Cheat out{};
+ out.raw = ParseSingleLineCheat(line);
+ block.push_back(out);
+ }
+
+ (master ? master_list : standard_list).emplace_back(patch_name, block);
+ }
+ }
+
+ return MakeCheatList(system, master_list, standard_list);
+}
+
+std::array<u8, 16> TextCheatParser::ParseSingleLineCheat(const std::string& line) const {
+ std::array<u8, 16> out{};
+
+ if (line.size() < 8)
+ return out;
+
+ const auto word1 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data(), 8});
+ std::memcpy(out.data(), word1.data(), sizeof(u32));
+
+ if (line.size() < 17 || line[8] != ' ')
+ return out;
+
+ const auto word2 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 9, 8});
+ std::memcpy(out.data() + sizeof(u32), word2.data(), sizeof(u32));
+
+ if (line.size() < 26 || line[17] != ' ') {
+ // Perform shifting in case value is truncated early.
+ const auto type = static_cast<CodeType>((out[0] & 0xF0) >> 4);
+ if (type == CodeType::Loop || type == CodeType::LoadImmediate ||
+ type == CodeType::StoreIndexed || type == CodeType::RegisterArithmetic) {
+ std::memcpy(out.data() + 8, out.data() + 4, sizeof(u32));
+ std::memset(out.data() + 4, 0, sizeof(u32));
+ }
+
+ return out;
+ }
+
+ const auto word3 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 18, 8});
+ std::memcpy(out.data() + 2 * sizeof(u32), word3.data(), sizeof(u32));
+
+ if (line.size() < 35 || line[26] != ' ') {
+ // Perform shifting in case value is truncated early.
+ const auto type = static_cast<CodeType>((out[0] & 0xF0) >> 4);
+ if (type == CodeType::WriteImmediate || type == CodeType::Conditional) {
+ std::memcpy(out.data() + 12, out.data() + 8, sizeof(u32));
+ std::memset(out.data() + 8, 0, sizeof(u32));
+ }
+
+ return out;
+ }
+
+ const auto word4 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 27, 8});
+ std::memcpy(out.data() + 3 * sizeof(u32), word4.data(), sizeof(u32));
+
+ return out;
+}
+
+namespace {
+u64 MemoryReadImpl(u32 width, VAddr addr) {
+ switch (width) {
+ case 1:
+ return Memory::Read8(addr);
+ case 2:
+ return Memory::Read16(addr);
+ case 4:
+ return Memory::Read32(addr);
+ case 8:
+ return Memory::Read64(addr);
+ default:
+ UNREACHABLE();
+ return 0;
+ }
+}
+
+void MemoryWriteImpl(u32 width, VAddr addr, u64 value) {
+ switch (width) {
+ case 1:
+ Memory::Write8(addr, static_cast<u8>(value));
+ break;
+ case 2:
+ Memory::Write16(addr, static_cast<u16>(value));
+ break;
+ case 4:
+ Memory::Write32(addr, static_cast<u32>(value));
+ break;
+ case 8:
+ Memory::Write64(addr, value);
+ break;
+ default:
+ UNREACHABLE();
+ }
+}
+} // Anonymous namespace
+
+CheatEngine::CheatEngine(Core::System& system, std::vector<CheatList> cheats_,
+ const std::string& build_id, VAddr code_region_start,
+ VAddr code_region_end)
+ : cheats{std::move(cheats_)}, core_timing{system.CoreTiming()} {
+ event = core_timing.RegisterEvent(
+ "CheatEngine::FrameCallback::" + build_id,
+ [this](u64 userdata, s64 cycles_late) { FrameCallback(userdata, cycles_late); });
+ core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS, event);
+
+ const auto& vm_manager = system.CurrentProcess()->VMManager();
+ for (auto& list : this->cheats) {
+ list.SetMemoryParameters(code_region_start, vm_manager.GetHeapRegionBaseAddress(),
+ code_region_end, vm_manager.GetHeapRegionEndAddress(),
+ &MemoryWriteImpl, &MemoryReadImpl);
+ }
+}
+
+CheatEngine::~CheatEngine() {
+ core_timing.UnscheduleEvent(event, 0);
+}
+
+void CheatEngine::FrameCallback(u64 userdata, s64 cycles_late) {
+ for (auto& list : cheats) {
+ list.Execute();
+ }
+
+ core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS - cycles_late, event);
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/cheat_engine.h b/src/core/file_sys/cheat_engine.h
new file mode 100644
index 000000000..ac22a82cb
--- /dev/null
+++ b/src/core/file_sys/cheat_engine.h
@@ -0,0 +1,234 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <map>
+#include <set>
+#include <vector>
+#include "common/bit_field.h"
+#include "common/common_types.h"
+
+namespace Core {
+class System;
+}
+
+namespace Core::Timing {
+class CoreTiming;
+struct EventType;
+} // namespace Core::Timing
+
+namespace FileSys {
+
+enum class CodeType : u32 {
+ // 0TMR00AA AAAAAAAA YYYYYYYY YYYYYYYY
+ // Writes a T sized value Y to the address A added to the value of register R in memory domain M
+ WriteImmediate = 0,
+
+ // 1TMC00AA AAAAAAAA YYYYYYYY YYYYYYYY
+ // Compares the T sized value Y to the value at address A in memory domain M using the
+ // conditional function C. If success, continues execution. If failure, jumps to the matching
+ // EndConditional statement.
+ Conditional = 1,
+
+ // 20000000
+ // Terminates a Conditional or ConditionalInput block.
+ EndConditional = 2,
+
+ // 300R0000 VVVVVVVV
+ // Starts looping V times, storing the current count in register R.
+ // Loop block is terminated with a matching 310R0000.
+ Loop = 3,
+
+ // 400R0000 VVVVVVVV VVVVVVVV
+ // Sets the value of register R to the value V.
+ LoadImmediate = 4,
+
+ // 5TMRI0AA AAAAAAAA
+ // Sets the value of register R to the value of width T at address A in memory domain M, with
+ // the current value of R added to the address if I == 1.
+ LoadIndexed = 5,
+
+ // 6T0RIFG0 VVVVVVVV VVVVVVVV
+ // Writes the value V of width T to the memory address stored in register R. Adds the value of
+ // register G to the final calculation if F is nonzero. Increments the value of register R by T
+ // after operation if I is nonzero.
+ StoreIndexed = 6,
+
+ // 7T0RA000 VVVVVVVV
+ // Performs the arithmetic operation A on the value in register R and the value V of width T,
+ // storing the result in register R.
+ RegisterArithmetic = 7,
+
+ // 8KKKKKKK
+ // Checks to see if any of the buttons defined by the bitmask K are pressed. If any are,
+ // execution continues. If none are, execution skips to the next EndConditional command.
+ ConditionalInput = 8,
+};
+
+enum class MemoryType : u32 {
+ // Addressed relative to start of main NSO
+ MainNSO = 0,
+
+ // Addressed relative to start of heap
+ Heap = 1,
+};
+
+enum class ArithmeticOp : u32 {
+ Add = 0,
+ Sub = 1,
+ Mult = 2,
+ LShift = 3,
+ RShift = 4,
+};
+
+enum class ComparisonOp : u32 {
+ GreaterThan = 1,
+ GreaterThanEqual = 2,
+ LessThan = 3,
+ LessThanEqual = 4,
+ Equal = 5,
+ Inequal = 6,
+};
+
+union Cheat {
+ std::array<u8, 16> raw;
+
+ BitField<4, 4, CodeType> type;
+ BitField<0, 4, u32> width; // Can be 1, 2, 4, or 8. Measured in bytes.
+ BitField<0, 4, u32> end_of_loop;
+ BitField<12, 4, MemoryType> memory_type;
+ BitField<8, 4, u32> register_3;
+ BitField<8, 4, ComparisonOp> comparison_op;
+ BitField<20, 4, u32> load_from_register;
+ BitField<20, 4, u32> increment_register;
+ BitField<20, 4, ArithmeticOp> arithmetic_op;
+ BitField<16, 4, u32> add_additional_register;
+ BitField<28, 4, u32> register_6;
+
+ u64 Address() const;
+ u64 ValueWidth(u64 offset) const;
+ u64 Value(u64 offset, u64 width) const;
+ u32 KeypadValue() const;
+};
+
+class CheatParser;
+
+// Represents a full collection of cheats for a game. The Execute function should be called every
+// interval that all cheats should be executed. Clients should not directly instantiate this class
+// (hence private constructor), they should instead receive an instance from CheatParser, which
+// guarantees the list is always in an acceptable state.
+class CheatList {
+public:
+ friend class CheatParser;
+
+ using Block = std::vector<Cheat>;
+ using ProgramSegment = std::vector<std::pair<std::string, Block>>;
+
+ // (width in bytes, address, value)
+ using MemoryWriter = void (*)(u32, VAddr, u64);
+ // (width in bytes, address) -> value
+ using MemoryReader = u64 (*)(u32, VAddr);
+
+ void SetMemoryParameters(VAddr main_begin, VAddr heap_begin, VAddr main_end, VAddr heap_end,
+ MemoryWriter writer, MemoryReader reader);
+
+ void Execute();
+
+private:
+ CheatList(const Core::System& system_, ProgramSegment master, ProgramSegment standard);
+
+ void ProcessBlockPairs(const Block& block);
+ void ExecuteSingleCheat(const Cheat& cheat);
+
+ void ExecuteBlock(const Block& block);
+
+ bool EvaluateConditional(const Cheat& cheat) const;
+
+ // Individual cheat operations
+ void WriteImmediate(const Cheat& cheat);
+ void BeginConditional(const Cheat& cheat);
+ void EndConditional(const Cheat& cheat);
+ void Loop(const Cheat& cheat);
+ void LoadImmediate(const Cheat& cheat);
+ void LoadIndexed(const Cheat& cheat);
+ void StoreIndexed(const Cheat& cheat);
+ void RegisterArithmetic(const Cheat& cheat);
+ void BeginConditionalInput(const Cheat& cheat);
+
+ VAddr SanitizeAddress(VAddr in) const;
+
+ // Master Codes are defined as codes that cannot be disabled and are run prior to all
+ // others.
+ ProgramSegment master_list;
+ // All other codes
+ ProgramSegment standard_list;
+
+ bool in_standard = false;
+
+ // 16 (0x0-0xF) scratch registers that can be used by cheats
+ std::array<u64, 16> scratch{};
+
+ MemoryWriter writer = nullptr;
+ MemoryReader reader = nullptr;
+
+ u64 main_region_begin{};
+ u64 heap_region_begin{};
+ u64 main_region_end{};
+ u64 heap_region_end{};
+
+ u64 current_block{};
+ // The current index of the cheat within the current Block
+ u64 current_index{};
+
+ // The 'stack' of the program. When a conditional or loop statement is encountered, its index is
+ // pushed onto this queue. When a end block is encountered, the condition is checked.
+ std::map<u64, u64> block_pairs;
+
+ std::set<u64> encountered_loops;
+
+ const Core::System* system;
+};
+
+// Intermediary class that parses a text file or other disk format for storing cheats into a
+// CheatList object, that can be used for execution.
+class CheatParser {
+public:
+ virtual ~CheatParser();
+
+ virtual CheatList Parse(const Core::System& system, const std::vector<u8>& data) const = 0;
+
+protected:
+ CheatList MakeCheatList(const Core::System& system_, CheatList::ProgramSegment master,
+ CheatList::ProgramSegment standard) const;
+};
+
+// CheatParser implementation that parses text files
+class TextCheatParser final : public CheatParser {
+public:
+ ~TextCheatParser() override;
+
+ CheatList Parse(const Core::System& system, const std::vector<u8>& data) const override;
+
+private:
+ std::array<u8, 16> ParseSingleLineCheat(const std::string& line) const;
+};
+
+// Class that encapsulates a CheatList and manages its interaction with memory and CoreTiming
+class CheatEngine final {
+public:
+ CheatEngine(Core::System& system_, std::vector<CheatList> cheats_, const std::string& build_id,
+ VAddr code_region_start, VAddr code_region_end);
+ ~CheatEngine();
+
+private:
+ void FrameCallback(u64 userdata, s64 cycles_late);
+
+ std::vector<CheatList> cheats;
+
+ Core::Timing::EventType* event;
+ Core::Timing::CoreTiming& core_timing;
+};
+
+} // namespace FileSys
diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h
index 5d4d05c82..15b9e6624 100644
--- a/src/core/file_sys/content_archive.h
+++ b/src/core/file_sys/content_archive.h
@@ -24,13 +24,26 @@ namespace FileSys {
union NCASectionHeader;
+/// Describes the type of content within an NCA archive.
enum class NCAContentType : u8 {
+ /// Executable-related data
Program = 0,
+
+ /// Metadata.
Meta = 1,
+
+ /// Access control data.
Control = 2,
+
+ /// Information related to the game manual
+ /// e.g. Legal information, etc.
Manual = 3,
+
+ /// System data.
Data = 4,
- Data_Unknown5 = 5, ///< Seems to be used on some system archives
+
+ /// Data that can be accessed by applications.
+ PublicData = 5,
};
enum class NCASectionCryptoType : u8 {
diff --git a/src/core/file_sys/control_metadata.cpp b/src/core/file_sys/control_metadata.cpp
index 83c184750..f155a1341 100644
--- a/src/core/file_sys/control_metadata.cpp
+++ b/src/core/file_sys/control_metadata.cpp
@@ -67,7 +67,7 @@ std::string NACP::GetDeveloperName(Language language) const {
}
u64 NACP::GetTitleId() const {
- return raw.title_id;
+ return raw.save_data_owner_id;
}
u64 NACP::GetDLCBaseTitleId() const {
@@ -80,11 +80,19 @@ std::string NACP::GetVersionString() const {
}
u64 NACP::GetDefaultNormalSaveSize() const {
- return raw.normal_save_data_size;
+ return raw.user_account_save_data_size;
}
u64 NACP::GetDefaultJournalSaveSize() const {
- return raw.journal_sava_data_size;
+ return raw.user_account_save_data_journal_size;
+}
+
+bool NACP::GetUserAccountSwitchLock() const {
+ return raw.user_account_switch_lock != 0;
+}
+
+u32 NACP::GetSupportedLanguages() const {
+ return raw.supported_languages;
}
std::vector<u8> NACP::GetRawBytes() const {
diff --git a/src/core/file_sys/control_metadata.h b/src/core/file_sys/control_metadata.h
index 7b9cdc910..2d8c251ac 100644
--- a/src/core/file_sys/control_metadata.h
+++ b/src/core/file_sys/control_metadata.h
@@ -30,7 +30,8 @@ struct RawNACP {
std::array<LanguageEntry, 16> language_entries;
std::array<u8, 0x25> isbn;
u8 startup_user_account;
- INSERT_PADDING_BYTES(2);
+ u8 user_account_switch_lock;
+ u8 addon_content_registration_type;
u32_le application_attribute;
u32_le supported_languages;
u32_le parental_control;
@@ -38,23 +39,35 @@ struct RawNACP {
u8 video_capture_mode;
bool data_loss_confirmation;
INSERT_PADDING_BYTES(1);
- u64_le title_id;
+ u64_le presence_group_id;
std::array<u8, 0x20> rating_age;
std::array<char, 0x10> version_string;
u64_le dlc_base_title_id;
- u64_le title_id_2;
- u64_le normal_save_data_size;
- u64_le journal_sava_data_size;
- INSERT_PADDING_BYTES(0x18);
- u64_le product_code;
+ u64_le save_data_owner_id;
+ u64_le user_account_save_data_size;
+ u64_le user_account_save_data_journal_size;
+ u64_le device_save_data_size;
+ u64_le device_save_data_journal_size;
+ u64_le bcat_delivery_cache_storage_size;
+ char application_error_code_category[8];
std::array<u64_le, 0x8> local_communication;
u8 logo_type;
u8 logo_handling;
bool runtime_add_on_content_install;
INSERT_PADDING_BYTES(5);
- u64_le title_id_update;
- std::array<u8, 0x40> bcat_passphrase;
- INSERT_PADDING_BYTES(0xEC0);
+ u64_le seed_for_pseudo_device_id;
+ std::array<u8, 0x41> bcat_passphrase;
+ INSERT_PADDING_BYTES(7);
+ u64_le user_account_save_data_max_size;
+ u64_le user_account_save_data_max_journal_size;
+ u64_le device_save_data_max_size;
+ u64_le device_save_data_max_journal_size;
+ u64_le temporary_storage_size;
+ u64_le cache_storage_size;
+ u64_le cache_storage_journal_size;
+ u64_le cache_storage_data_and_journal_max_size;
+ u64_le cache_storage_max_index;
+ INSERT_PADDING_BYTES(0xE70);
};
static_assert(sizeof(RawNACP) == 0x4000, "RawNACP has incorrect size.");
@@ -97,7 +110,9 @@ public:
std::string GetVersionString() const;
u64 GetDefaultNormalSaveSize() const;
u64 GetDefaultJournalSaveSize() const;
+ u32 GetSupportedLanguages() const;
std::vector<u8> GetRawBytes() const;
+ bool GetUserAccountSwitchLock() const;
private:
RawNACP raw{};
diff --git a/src/core/file_sys/errors.h b/src/core/file_sys/errors.h
index e4a4ee4ab..bb4654366 100644
--- a/src/core/file_sys/errors.h
+++ b/src/core/file_sys/errors.h
@@ -11,6 +11,9 @@ namespace FileSys {
constexpr ResultCode ERROR_PATH_NOT_FOUND{ErrorModule::FS, 1};
constexpr ResultCode ERROR_ENTITY_NOT_FOUND{ErrorModule::FS, 1002};
constexpr ResultCode ERROR_SD_CARD_NOT_FOUND{ErrorModule::FS, 2001};
+constexpr ResultCode ERROR_OUT_OF_BOUNDS{ErrorModule::FS, 3005};
+constexpr ResultCode ERROR_FAILED_MOUNT_ARCHIVE{ErrorModule::FS, 3223};
+constexpr ResultCode ERROR_INVALID_ARGUMENT{ErrorModule::FS, 6001};
constexpr ResultCode ERROR_INVALID_OFFSET{ErrorModule::FS, 6061};
constexpr ResultCode ERROR_INVALID_SIZE{ErrorModule::FS, 6062};
diff --git a/src/core/file_sys/fsmitm_romfsbuild.cpp b/src/core/file_sys/fsmitm_romfsbuild.cpp
index 47b7526c7..d126ae8dd 100644
--- a/src/core/file_sys/fsmitm_romfsbuild.cpp
+++ b/src/core/file_sys/fsmitm_romfsbuild.cpp
@@ -23,6 +23,7 @@
*/
#include <cstring>
+#include <string_view>
#include "common/alignment.h"
#include "common/assert.h"
#include "core/file_sys/fsmitm_romfsbuild.h"
@@ -97,7 +98,8 @@ struct RomFSBuildFileContext {
VirtualFile source;
};
-static u32 romfs_calc_path_hash(u32 parent, std::string path, u32 start, std::size_t path_len) {
+static u32 romfs_calc_path_hash(u32 parent, std::string_view path, u32 start,
+ std::size_t path_len) {
u32 hash = parent ^ 123456789;
for (u32 i = 0; i < path_len; i++) {
hash = (hash >> 5) | (hash << 27);
diff --git a/src/core/file_sys/ips_layer.cpp b/src/core/file_sys/ips_layer.cpp
index 485c4913a..a08a70efd 100644
--- a/src/core/file_sys/ips_layer.cpp
+++ b/src/core/file_sys/ips_layer.cpp
@@ -287,7 +287,6 @@ void IPSwitchCompiler::Parse() {
} else {
// hex replacement
const auto value = patch_line.substr(9);
- replace.reserve(value.size() / 2);
replace = Common::HexStringToVector(value, is_little_endian);
}
@@ -295,7 +294,7 @@ void IPSwitchCompiler::Parse() {
LOG_INFO(Loader,
"[IPSwitchCompiler ('{}')] - Patching value at offset 0x{:08X} "
"with byte string '{}'",
- patch_text->GetName(), offset, Common::HexVectorToString(replace));
+ patch_text->GetName(), offset, Common::HexToString(replace));
}
patch.records.insert_or_assign(offset, std::move(replace));
diff --git a/src/core/file_sys/nca_metadata.cpp b/src/core/file_sys/nca_metadata.cpp
index 6f34b7836..93d0df6b9 100644
--- a/src/core/file_sys/nca_metadata.cpp
+++ b/src/core/file_sys/nca_metadata.cpp
@@ -10,14 +10,6 @@
namespace FileSys {
-bool operator>=(TitleType lhs, TitleType rhs) {
- return static_cast<std::size_t>(lhs) >= static_cast<std::size_t>(rhs);
-}
-
-bool operator<=(TitleType lhs, TitleType rhs) {
- return static_cast<std::size_t>(lhs) <= static_cast<std::size_t>(rhs);
-}
-
CNMT::CNMT(VirtualFile file) {
if (file->ReadObject(&header) != sizeof(CNMTHeader))
return;
diff --git a/src/core/file_sys/nca_metadata.h b/src/core/file_sys/nca_metadata.h
index a05d155f4..84d5cd1e0 100644
--- a/src/core/file_sys/nca_metadata.h
+++ b/src/core/file_sys/nca_metadata.h
@@ -4,6 +4,7 @@
#pragma once
+#include <array>
#include <memory>
#include <vector>
#include "common/common_funcs.h"
@@ -29,9 +30,6 @@ enum class TitleType : u8 {
DeltaTitle = 0x83,
};
-bool operator>=(TitleType lhs, TitleType rhs);
-bool operator<=(TitleType lhs, TitleType rhs);
-
enum class ContentRecordType : u8 {
Meta = 0,
Program = 1,
@@ -72,11 +70,15 @@ struct CNMTHeader {
u64_le title_id;
u32_le title_version;
TitleType type;
- INSERT_PADDING_BYTES(1);
+ u8 reserved;
u16_le table_offset;
u16_le number_content_entries;
u16_le number_meta_entries;
- INSERT_PADDING_BYTES(12);
+ u8 attributes;
+ std::array<u8, 2> reserved2;
+ u8 is_committed;
+ u32_le required_download_system_version;
+ std::array<u8, 4> reserved3;
};
static_assert(sizeof(CNMTHeader) == 0x20, "CNMTHeader has incorrect size.");
diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp
index 61706966e..da823c37b 100644
--- a/src/core/file_sys/patch_manager.cpp
+++ b/src/core/file_sys/patch_manager.cpp
@@ -7,8 +7,10 @@
#include <cstddef>
#include <cstring>
+#include "common/file_util.h"
#include "common/hex_util.h"
#include "common/logging/log.h"
+#include "core/core.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/ips_layer.h"
@@ -19,6 +21,7 @@
#include "core/file_sys/vfs_vector.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/loader/loader.h"
+#include "core/loader/nso.h"
#include "core/settings.h"
namespace FileSys {
@@ -31,14 +34,6 @@ constexpr std::array<const char*, 14> EXEFS_FILE_NAMES{
"subsdk3", "subsdk4", "subsdk5", "subsdk6", "subsdk7", "subsdk8", "subsdk9",
};
-struct NSOBuildHeader {
- u32_le magic;
- INSERT_PADDING_BYTES(0x3C);
- std::array<u8, 0x20> build_id;
- INSERT_PADDING_BYTES(0xA0);
-};
-static_assert(sizeof(NSOBuildHeader) == 0x100, "NSOBuildHeader has incorrect size.");
-
std::string FormatTitleVersion(u32 version, TitleVersionFormat format) {
std::array<u8, sizeof(u32)> bytes{};
bytes[0] = version % SINGLE_BYTE_MODULUS;
@@ -75,7 +70,7 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
}
}
- const auto installed = Service::FileSystem::GetUnionContents();
+ const auto& installed = Core::System::GetInstance().GetContentProvider();
const auto& disabled = Settings::values.disabled_addons[title_id];
const auto update_disabled =
@@ -147,7 +142,7 @@ std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualD
if (!compiler.IsValid())
continue;
- auto this_build_id = Common::HexArrayToString(compiler.GetBuildID());
+ auto this_build_id = Common::HexToString(compiler.GetBuildID());
this_build_id =
this_build_id.substr(0, this_build_id.find_last_not_of('0') + 1);
@@ -161,32 +156,35 @@ std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualD
return out;
}
-std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso) const {
- if (nso.size() < 0x100)
+std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso, const std::string& name) const {
+ if (nso.size() < sizeof(Loader::NSOHeader)) {
return nso;
+ }
- NSOBuildHeader header;
- std::memcpy(&header, nso.data(), sizeof(NSOBuildHeader));
+ Loader::NSOHeader header;
+ std::memcpy(&header, nso.data(), sizeof(header));
- if (header.magic != Common::MakeMagic('N', 'S', 'O', '0'))
+ if (header.magic != Common::MakeMagic('N', 'S', 'O', '0')) {
return nso;
+ }
- const auto build_id_raw = Common::HexArrayToString(header.build_id);
+ const auto build_id_raw = Common::HexToString(header.build_id);
const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1);
if (Settings::values.dump_nso) {
- LOG_INFO(Loader, "Dumping NSO for build_id={}, title_id={:016X}", build_id, title_id);
+ LOG_INFO(Loader, "Dumping NSO for name={}, build_id={}, title_id={:016X}", name, build_id,
+ title_id);
const auto dump_dir = Service::FileSystem::GetModificationDumpRoot(title_id);
if (dump_dir != nullptr) {
const auto nso_dir = GetOrCreateDirectoryRelative(dump_dir, "/nso");
- const auto file = nso_dir->CreateFile(fmt::format("{}.nso", build_id));
+ const auto file = nso_dir->CreateFile(fmt::format("{}-{}.nso", name, build_id));
file->Resize(nso.size());
file->WriteBytes(nso);
}
}
- LOG_INFO(Loader, "Patching NSO for build_id={}", build_id);
+ LOG_INFO(Loader, "Patching NSO for name={}, build_id={}", name, build_id);
const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
auto patch_dirs = load_dir->GetSubdirectories();
@@ -212,14 +210,16 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso) const {
}
}
- if (out.size() < 0x100)
+ if (out.size() < sizeof(Loader::NSOHeader)) {
return nso;
- std::memcpy(out.data(), &header, sizeof(NSOBuildHeader));
+ }
+
+ std::memcpy(out.data(), &header, sizeof(header));
return out;
}
bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const {
- const auto build_id_raw = Common::HexArrayToString(build_id_);
+ 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);
@@ -232,6 +232,57 @@ bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const {
return !CollectPatches(patch_dirs, build_id).empty();
}
+static std::optional<CheatList> 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;
+ }
+
+ TextCheatParser parser;
+ return parser.Parse(system, data);
+}
+
+std::vector<CheatList> PatchManager::CreateCheatList(const Core::System& system,
+ const std::array<u8, 32>& build_id_) const {
+ const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
+ auto patch_dirs = load_dir->GetSubdirectories();
+ std::sort(patch_dirs.begin(), patch_dirs.end(),
+ [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
+
+ std::vector<CheatList> out;
+ out.reserve(patch_dirs.size());
+ for (const auto& subdir : patch_dirs) {
+ auto cheats_dir = subdir->GetSubdirectory("cheats");
+ if (cheats_dir != nullptr) {
+ auto res = ReadCheatFileFromFolder(system, title_id, build_id_, cheats_dir, true);
+ if (res.has_value()) {
+ out.push_back(std::move(*res));
+ continue;
+ }
+
+ res = ReadCheatFileFromFolder(system, title_id, build_id_, cheats_dir, false);
+ if (res.has_value())
+ out.push_back(std::move(*res));
+ }
+ }
+
+ return out;
+}
+
static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type) {
const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
if ((type != ContentRecordType::Program && type != ContentRecordType::Data) ||
@@ -296,7 +347,7 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, Content
if (romfs == nullptr)
return romfs;
- const auto installed = Service::FileSystem::GetUnionContents();
+ const auto& installed = Core::System::GetInstance().GetContentProvider();
// Game Updates
const auto update_tid = GetUpdateTitleID(title_id);
@@ -343,7 +394,7 @@ static bool IsDirValidAndNonEmpty(const VirtualDir& dir) {
std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNames(
VirtualFile update_raw) const {
std::map<std::string, std::string, std::less<>> out;
- const auto installed = Service::FileSystem::GetUnionContents();
+ const auto& installed = Core::System::GetInstance().GetContentProvider();
const auto& disabled = Settings::values.disabled_addons[title_id];
// Game Updates
@@ -403,6 +454,8 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
}
if (IsDirValidAndNonEmpty(mod->GetSubdirectory("romfs")))
AppendCommaIfNotEmpty(types, "LayeredFS");
+ if (IsDirValidAndNonEmpty(mod->GetSubdirectory("cheats")))
+ AppendCommaIfNotEmpty(types, "Cheats");
if (types.empty())
continue;
@@ -415,10 +468,10 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
// DLC
const auto dlc_entries = installed.ListEntriesFilter(TitleType::AOC, ContentRecordType::Data);
- std::vector<RegisteredCacheEntry> dlc_match;
+ 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 RegisteredCacheEntry& entry) {
+ [this, &installed](const ContentProviderEntry& entry) {
return (entry.title_id & DLC_BASE_TITLE_ID_MASK) == title_id &&
installed.GetEntry(entry)->GetStatus() == Loader::ResultStatus::Success;
});
@@ -441,7 +494,7 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
}
std::pair<std::unique_ptr<NACP>, VirtualFile> PatchManager::GetControlMetadata() const {
- const auto installed{Service::FileSystem::GetUnionContents()};
+ const auto& installed = Core::System::GetInstance().GetContentProvider();
const auto base_control_nca = installed.GetEntry(title_id, ContentRecordType::Control);
if (base_control_nca == nullptr)
diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h
index b8a1652fd..769f8c6f0 100644
--- a/src/core/file_sys/patch_manager.h
+++ b/src/core/file_sys/patch_manager.h
@@ -8,9 +8,14 @@
#include <memory>
#include <string>
#include "common/common_types.h"
+#include "core/file_sys/cheat_engine.h"
#include "core/file_sys/nca_metadata.h"
#include "core/file_sys/vfs.h"
+namespace Core {
+class System;
+}
+
namespace FileSys {
class NCA;
@@ -39,12 +44,16 @@ public:
// Currently tracked NSO patches:
// - IPS
// - IPSwitch
- std::vector<u8> PatchNSO(const std::vector<u8>& nso) const;
+ 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;
+ // Creates a CheatList object with all
+ std::vector<CheatList> CreateCheatList(const Core::System& system,
+ const std::array<u8, 0x20>& build_id) const;
+
// Currently tracked RomFS patches:
// - Game Updates
// - LayeredFS
diff --git a/src/core/file_sys/program_metadata.cpp b/src/core/file_sys/program_metadata.cpp
index d3e00437f..d863253f8 100644
--- a/src/core/file_sys/program_metadata.cpp
+++ b/src/core/file_sys/program_metadata.cpp
@@ -3,7 +3,6 @@
// Refer to the license.txt file included.
#include <cstddef>
-#include <cstring>
#include <vector>
#include "common/logging/log.h"
@@ -17,28 +16,30 @@ ProgramMetadata::ProgramMetadata() = default;
ProgramMetadata::~ProgramMetadata() = default;
Loader::ResultStatus ProgramMetadata::Load(VirtualFile file) {
- std::size_t total_size = static_cast<std::size_t>(file->GetSize());
- if (total_size < sizeof(Header))
+ const std::size_t total_size = file->GetSize();
+ if (total_size < sizeof(Header)) {
return Loader::ResultStatus::ErrorBadNPDMHeader;
+ }
- // TODO(DarkLordZach): Use ReadObject when Header/AcidHeader becomes trivially copyable.
- std::vector<u8> npdm_header_data = file->ReadBytes(sizeof(Header));
- if (sizeof(Header) != npdm_header_data.size())
+ if (sizeof(Header) != file->ReadObject(&npdm_header)) {
return Loader::ResultStatus::ErrorBadNPDMHeader;
- std::memcpy(&npdm_header, npdm_header_data.data(), sizeof(Header));
+ }
- std::vector<u8> acid_header_data = file->ReadBytes(sizeof(AcidHeader), npdm_header.acid_offset);
- if (sizeof(AcidHeader) != acid_header_data.size())
+ if (sizeof(AcidHeader) != file->ReadObject(&acid_header, npdm_header.acid_offset)) {
return Loader::ResultStatus::ErrorBadACIDHeader;
- std::memcpy(&acid_header, acid_header_data.data(), sizeof(AcidHeader));
+ }
- if (sizeof(AciHeader) != file->ReadObject(&aci_header, npdm_header.aci_offset))
+ if (sizeof(AciHeader) != file->ReadObject(&aci_header, npdm_header.aci_offset)) {
return Loader::ResultStatus::ErrorBadACIHeader;
+ }
- if (sizeof(FileAccessControl) != file->ReadObject(&acid_file_access, acid_header.fac_offset))
+ if (sizeof(FileAccessControl) != file->ReadObject(&acid_file_access, acid_header.fac_offset)) {
return Loader::ResultStatus::ErrorBadFileAccessControl;
- if (sizeof(FileAccessHeader) != file->ReadObject(&aci_file_access, aci_header.fah_offset))
+ }
+
+ if (sizeof(FileAccessHeader) != file->ReadObject(&aci_file_access, aci_header.fah_offset)) {
return Loader::ResultStatus::ErrorBadFileAccessHeader;
+ }
aci_kernel_capabilities.resize(aci_header.kac_size / sizeof(u32));
const u64 read_size = aci_header.kac_size;
diff --git a/src/core/file_sys/program_metadata.h b/src/core/file_sys/program_metadata.h
index 0033ba347..7de5b9cf9 100644
--- a/src/core/file_sys/program_metadata.h
+++ b/src/core/file_sys/program_metadata.h
@@ -58,7 +58,6 @@ public:
void Print() const;
private:
- // TODO(DarkLordZach): BitField is not trivially copyable.
struct Header {
std::array<char, 4> magic;
std::array<u8, 8> reserved;
@@ -85,7 +84,6 @@ private:
static_assert(sizeof(Header) == 0x80, "NPDM header structure size is wrong");
- // TODO(DarkLordZach): BitField is not trivially copyable.
struct AcidHeader {
std::array<u8, 0x100> signature;
std::array<u8, 0x100> nca_modulus;
diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp
index 128199063..58917e094 100644
--- a/src/core/file_sys/registered_cache.cpp
+++ b/src/core/file_sys/registered_cache.cpp
@@ -23,19 +23,19 @@ namespace FileSys {
// The size of blocks to use when vfs raw copying into nand.
constexpr size_t VFS_RC_LARGE_COPY_BLOCK = 0x400000;
-std::string RegisteredCacheEntry::DebugInfo() const {
+std::string ContentProviderEntry::DebugInfo() const {
return fmt::format("title_id={:016X}, content_type={:02X}", title_id, static_cast<u8>(type));
}
-bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs) {
+bool operator<(const ContentProviderEntry& lhs, const ContentProviderEntry& rhs) {
return (lhs.title_id < rhs.title_id) || (lhs.title_id == rhs.title_id && lhs.type < rhs.type);
}
-bool operator==(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs) {
+bool operator==(const ContentProviderEntry& lhs, const ContentProviderEntry& rhs) {
return std::tie(lhs.title_id, lhs.type) == std::tie(rhs.title_id, rhs.type);
}
-bool operator!=(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs) {
+bool operator!=(const ContentProviderEntry& lhs, const ContentProviderEntry& rhs) {
return !operator==(lhs, rhs);
}
@@ -53,13 +53,14 @@ static bool FollowsNcaIdFormat(std::string_view name) {
static std::string GetRelativePathFromNcaID(const std::array<u8, 16>& nca_id, bool second_hex_upper,
bool within_two_digit) {
- if (!within_two_digit)
- return fmt::format("/{}.nca", Common::HexArrayToString(nca_id, second_hex_upper));
+ if (!within_two_digit) {
+ return fmt::format("/{}.nca", Common::HexToString(nca_id, second_hex_upper));
+ }
Core::Crypto::SHA256Hash hash{};
mbedtls_sha256(nca_id.data(), nca_id.size(), hash.data(), 0);
return fmt::format("/000000{:02X}/{}.nca", hash[0],
- Common::HexArrayToString(nca_id, second_hex_upper));
+ Common::HexToString(nca_id, second_hex_upper));
}
static std::string GetCNMTName(TitleType type, u64 title_id) {
@@ -84,7 +85,7 @@ static std::string GetCNMTName(TitleType type, u64 title_id) {
return fmt::format("{}_{:016x}.cnmt", TITLE_TYPE_NAMES[index], title_id);
}
-static ContentRecordType GetCRTypeFromNCAType(NCAContentType type) {
+ContentRecordType GetCRTypeFromNCAType(NCAContentType type) {
switch (type) {
case NCAContentType::Program:
// TODO(DarkLordZach): Differentiate between Program and Patch
@@ -94,7 +95,7 @@ static ContentRecordType GetCRTypeFromNCAType(NCAContentType type) {
case NCAContentType::Control:
return ContentRecordType::Control;
case NCAContentType::Data:
- case NCAContentType::Data_Unknown5:
+ case NCAContentType::PublicData:
return ContentRecordType::Data;
case NCAContentType::Manual:
// TODO(DarkLordZach): Peek at NCA contents to differentiate Manual and Legal.
@@ -104,6 +105,28 @@ static ContentRecordType GetCRTypeFromNCAType(NCAContentType type) {
}
}
+ContentProvider::~ContentProvider() = default;
+
+bool ContentProvider::HasEntry(ContentProviderEntry entry) const {
+ return HasEntry(entry.title_id, entry.type);
+}
+
+VirtualFile ContentProvider::GetEntryUnparsed(ContentProviderEntry entry) const {
+ return GetEntryUnparsed(entry.title_id, entry.type);
+}
+
+VirtualFile ContentProvider::GetEntryRaw(ContentProviderEntry entry) const {
+ return GetEntryRaw(entry.title_id, entry.type);
+}
+
+std::unique_ptr<NCA> ContentProvider::GetEntry(ContentProviderEntry entry) const {
+ return GetEntry(entry.title_id, entry.type);
+}
+
+std::vector<ContentProviderEntry> ContentProvider::ListEntries() const {
+ return ListEntriesFilter(std::nullopt, std::nullopt, std::nullopt);
+}
+
VirtualFile RegisteredCache::OpenFileOrDirectoryConcat(const VirtualDir& dir,
std::string_view path) const {
const auto file = dir->GetFileRelative(path);
@@ -161,8 +184,8 @@ VirtualFile RegisteredCache::GetFileAtID(NcaID id) const {
return file;
}
-static std::optional<NcaID> CheckMapForContentRecord(
- const boost::container::flat_map<u64, CNMT>& map, u64 title_id, ContentRecordType type) {
+static std::optional<NcaID> CheckMapForContentRecord(const std::map<u64, CNMT>& map, u64 title_id,
+ ContentRecordType type) {
if (map.find(title_id) == map.end())
return {};
@@ -268,7 +291,7 @@ void RegisteredCache::Refresh() {
AccumulateYuzuMeta();
}
-RegisteredCache::RegisteredCache(VirtualDir dir_, RegisteredCacheParsingFunction parsing_function)
+RegisteredCache::RegisteredCache(VirtualDir dir_, ContentProviderParsingFunction parsing_function)
: dir(std::move(dir_)), parser(std::move(parsing_function)) {
Refresh();
}
@@ -279,19 +302,11 @@ bool RegisteredCache::HasEntry(u64 title_id, ContentRecordType type) const {
return GetEntryRaw(title_id, type) != nullptr;
}
-bool RegisteredCache::HasEntry(RegisteredCacheEntry entry) const {
- return GetEntryRaw(entry) != nullptr;
-}
-
VirtualFile RegisteredCache::GetEntryUnparsed(u64 title_id, ContentRecordType type) const {
const auto id = GetNcaIDFromMetadata(title_id, type);
return id ? GetFileAtID(*id) : nullptr;
}
-VirtualFile RegisteredCache::GetEntryUnparsed(RegisteredCacheEntry entry) const {
- return GetEntryUnparsed(entry.title_id, entry.type);
-}
-
std::optional<u32> RegisteredCache::GetEntryVersion(u64 title_id) const {
const auto meta_iter = meta.find(title_id);
if (meta_iter != meta.end())
@@ -309,10 +324,6 @@ VirtualFile RegisteredCache::GetEntryRaw(u64 title_id, ContentRecordType type) c
return id ? parser(GetFileAtID(*id), *id) : nullptr;
}
-VirtualFile RegisteredCache::GetEntryRaw(RegisteredCacheEntry entry) const {
- return GetEntryRaw(entry.title_id, entry.type);
-}
-
std::unique_ptr<NCA> RegisteredCache::GetEntry(u64 title_id, ContentRecordType type) const {
const auto raw = GetEntryRaw(title_id, type);
if (raw == nullptr)
@@ -320,10 +331,6 @@ std::unique_ptr<NCA> RegisteredCache::GetEntry(u64 title_id, ContentRecordType t
return std::make_unique<NCA>(raw, nullptr, 0, keys);
}
-std::unique_ptr<NCA> RegisteredCache::GetEntry(RegisteredCacheEntry entry) const {
- return GetEntry(entry.title_id, entry.type);
-}
-
template <typename T>
void RegisteredCache::IterateAllMetadata(
std::vector<T>& out, std::function<T(const CNMT&, const ContentRecord&)> proc,
@@ -348,25 +355,14 @@ void RegisteredCache::IterateAllMetadata(
}
}
-std::vector<RegisteredCacheEntry> RegisteredCache::ListEntries() const {
- std::vector<RegisteredCacheEntry> out;
- IterateAllMetadata<RegisteredCacheEntry>(
- out,
- [](const CNMT& c, const ContentRecord& r) {
- return RegisteredCacheEntry{c.GetTitleID(), r.type};
- },
- [](const CNMT& c, const ContentRecord& r) { return true; });
- return out;
-}
-
-std::vector<RegisteredCacheEntry> RegisteredCache::ListEntriesFilter(
+std::vector<ContentProviderEntry> RegisteredCache::ListEntriesFilter(
std::optional<TitleType> title_type, std::optional<ContentRecordType> record_type,
std::optional<u64> title_id) const {
- std::vector<RegisteredCacheEntry> out;
- IterateAllMetadata<RegisteredCacheEntry>(
+ std::vector<ContentProviderEntry> out;
+ IterateAllMetadata<ContentProviderEntry>(
out,
[](const CNMT& c, const ContentRecord& r) {
- return RegisteredCacheEntry{c.GetTitleID(), r.type};
+ return ContentProviderEntry{c.GetTitleID(), r.type};
},
[&title_type, &record_type, &title_id](const CNMT& c, const ContentRecord& r) {
if (title_type && *title_type != c.GetType())
@@ -381,10 +377,11 @@ std::vector<RegisteredCacheEntry> RegisteredCache::ListEntriesFilter(
}
static std::shared_ptr<NCA> GetNCAFromNSPForID(const NSP& nsp, const NcaID& id) {
- const auto file = nsp.GetFile(fmt::format("{}.nca", Common::HexArrayToString(id, false)));
- if (file == nullptr)
+ auto file = nsp.GetFile(fmt::format("{}.nca", Common::HexToString(id, false)));
+ if (file == nullptr) {
return nullptr;
- return std::make_shared<NCA>(file);
+ }
+ return std::make_shared<NCA>(std::move(file));
}
InstallResult RegisteredCache::InstallEntry(const XCI& xci, bool overwrite_if_exists,
@@ -521,37 +518,56 @@ bool RegisteredCache::RawInstallYuzuMeta(const CNMT& cnmt) {
}) != yuzu_meta.end();
}
-RegisteredCacheUnion::RegisteredCacheUnion(std::vector<RegisteredCache*> caches)
- : caches(std::move(caches)) {}
+ContentProviderUnion::~ContentProviderUnion() = default;
-void RegisteredCacheUnion::Refresh() {
- for (const auto& c : caches)
- c->Refresh();
+void ContentProviderUnion::SetSlot(ContentProviderUnionSlot slot, ContentProvider* provider) {
+ providers[slot] = provider;
}
-bool RegisteredCacheUnion::HasEntry(u64 title_id, ContentRecordType type) const {
- return std::any_of(caches.begin(), caches.end(), [title_id, type](const auto& cache) {
- return cache->HasEntry(title_id, type);
- });
+void ContentProviderUnion::ClearSlot(ContentProviderUnionSlot slot) {
+ providers[slot] = nullptr;
}
-bool RegisteredCacheUnion::HasEntry(RegisteredCacheEntry entry) const {
- return HasEntry(entry.title_id, entry.type);
+void ContentProviderUnion::Refresh() {
+ for (auto& provider : providers) {
+ if (provider.second == nullptr)
+ continue;
+
+ provider.second->Refresh();
+ }
}
-std::optional<u32> RegisteredCacheUnion::GetEntryVersion(u64 title_id) const {
- for (const auto& c : caches) {
- const auto res = c->GetEntryVersion(title_id);
- if (res)
+bool ContentProviderUnion::HasEntry(u64 title_id, ContentRecordType type) const {
+ for (const auto& provider : providers) {
+ if (provider.second == nullptr)
+ continue;
+
+ if (provider.second->HasEntry(title_id, type))
+ return true;
+ }
+
+ return false;
+}
+
+std::optional<u32> ContentProviderUnion::GetEntryVersion(u64 title_id) const {
+ for (const auto& provider : providers) {
+ if (provider.second == nullptr)
+ continue;
+
+ const auto res = provider.second->GetEntryVersion(title_id);
+ if (res != std::nullopt)
return res;
}
- return {};
+ return std::nullopt;
}
-VirtualFile RegisteredCacheUnion::GetEntryUnparsed(u64 title_id, ContentRecordType type) const {
- for (const auto& c : caches) {
- const auto res = c->GetEntryUnparsed(title_id, type);
+VirtualFile ContentProviderUnion::GetEntryUnparsed(u64 title_id, ContentRecordType type) const {
+ for (const auto& provider : providers) {
+ if (provider.second == nullptr)
+ continue;
+
+ const auto res = provider.second->GetEntryUnparsed(title_id, type);
if (res != nullptr)
return res;
}
@@ -559,13 +575,12 @@ VirtualFile RegisteredCacheUnion::GetEntryUnparsed(u64 title_id, ContentRecordTy
return nullptr;
}
-VirtualFile RegisteredCacheUnion::GetEntryUnparsed(RegisteredCacheEntry entry) const {
- return GetEntryUnparsed(entry.title_id, entry.type);
-}
+VirtualFile ContentProviderUnion::GetEntryRaw(u64 title_id, ContentRecordType type) const {
+ for (const auto& provider : providers) {
+ if (provider.second == nullptr)
+ continue;
-VirtualFile RegisteredCacheUnion::GetEntryRaw(u64 title_id, ContentRecordType type) const {
- for (const auto& c : caches) {
- const auto res = c->GetEntryRaw(title_id, type);
+ const auto res = provider.second->GetEntryRaw(title_id, type);
if (res != nullptr)
return res;
}
@@ -573,30 +588,56 @@ VirtualFile RegisteredCacheUnion::GetEntryRaw(u64 title_id, ContentRecordType ty
return nullptr;
}
-VirtualFile RegisteredCacheUnion::GetEntryRaw(RegisteredCacheEntry entry) const {
- return GetEntryRaw(entry.title_id, entry.type);
-}
+std::unique_ptr<NCA> ContentProviderUnion::GetEntry(u64 title_id, ContentRecordType type) const {
+ for (const auto& provider : providers) {
+ if (provider.second == nullptr)
+ continue;
-std::unique_ptr<NCA> RegisteredCacheUnion::GetEntry(u64 title_id, ContentRecordType type) const {
- const auto raw = GetEntryRaw(title_id, type);
- if (raw == nullptr)
- return nullptr;
- return std::make_unique<NCA>(raw);
+ auto res = provider.second->GetEntry(title_id, type);
+ if (res != nullptr)
+ return res;
+ }
+
+ return nullptr;
}
-std::unique_ptr<NCA> RegisteredCacheUnion::GetEntry(RegisteredCacheEntry entry) const {
- return GetEntry(entry.title_id, entry.type);
+std::vector<ContentProviderEntry> ContentProviderUnion::ListEntriesFilter(
+ std::optional<TitleType> title_type, std::optional<ContentRecordType> record_type,
+ std::optional<u64> title_id) const {
+ std::vector<ContentProviderEntry> out;
+
+ for (const auto& provider : providers) {
+ if (provider.second == nullptr)
+ continue;
+
+ const auto vec = provider.second->ListEntriesFilter(title_type, record_type, title_id);
+ std::copy(vec.begin(), vec.end(), std::back_inserter(out));
+ }
+
+ std::sort(out.begin(), out.end());
+ out.erase(std::unique(out.begin(), out.end()), out.end());
+ return out;
}
-std::vector<RegisteredCacheEntry> RegisteredCacheUnion::ListEntries() const {
- std::vector<RegisteredCacheEntry> out;
- for (const auto& c : caches) {
- c->IterateAllMetadata<RegisteredCacheEntry>(
- out,
- [](const CNMT& c, const ContentRecord& r) {
- return RegisteredCacheEntry{c.GetTitleID(), r.type};
- },
- [](const CNMT& c, const ContentRecord& r) { return true; });
+std::vector<std::pair<ContentProviderUnionSlot, ContentProviderEntry>>
+ContentProviderUnion::ListEntriesFilterOrigin(std::optional<ContentProviderUnionSlot> origin,
+ std::optional<TitleType> title_type,
+ std::optional<ContentRecordType> record_type,
+ std::optional<u64> title_id) const {
+ std::vector<std::pair<ContentProviderUnionSlot, ContentProviderEntry>> out;
+
+ for (const auto& provider : providers) {
+ if (provider.second == nullptr)
+ continue;
+
+ if (origin.has_value() && *origin != provider.first)
+ continue;
+
+ const auto vec = provider.second->ListEntriesFilter(title_type, record_type, title_id);
+ std::transform(vec.begin(), vec.end(), std::back_inserter(out),
+ [&provider](const ContentProviderEntry& entry) {
+ return std::make_pair(provider.first, entry);
+ });
}
std::sort(out.begin(), out.end());
@@ -604,25 +645,61 @@ std::vector<RegisteredCacheEntry> RegisteredCacheUnion::ListEntries() const {
return out;
}
-std::vector<RegisteredCacheEntry> RegisteredCacheUnion::ListEntriesFilter(
+ManualContentProvider::~ManualContentProvider() = default;
+
+void ManualContentProvider::AddEntry(TitleType title_type, ContentRecordType content_type,
+ u64 title_id, VirtualFile file) {
+ entries.insert_or_assign({title_type, content_type, title_id}, file);
+}
+
+void ManualContentProvider::ClearAllEntries() {
+ entries.clear();
+}
+
+void ManualContentProvider::Refresh() {}
+
+bool ManualContentProvider::HasEntry(u64 title_id, ContentRecordType type) const {
+ return GetEntryRaw(title_id, type) != nullptr;
+}
+
+std::optional<u32> ManualContentProvider::GetEntryVersion(u64 title_id) const {
+ return std::nullopt;
+}
+
+VirtualFile ManualContentProvider::GetEntryUnparsed(u64 title_id, ContentRecordType type) const {
+ return GetEntryRaw(title_id, type);
+}
+
+VirtualFile ManualContentProvider::GetEntryRaw(u64 title_id, ContentRecordType type) const {
+ const auto iter =
+ std::find_if(entries.begin(), entries.end(), [title_id, type](const auto& entry) {
+ const auto [title_type, content_type, e_title_id] = entry.first;
+ return content_type == type && e_title_id == title_id;
+ });
+ if (iter == entries.end())
+ return nullptr;
+ return iter->second;
+}
+
+std::unique_ptr<NCA> ManualContentProvider::GetEntry(u64 title_id, ContentRecordType type) const {
+ const auto res = GetEntryRaw(title_id, type);
+ if (res == nullptr)
+ return nullptr;
+ return std::make_unique<NCA>(res, nullptr, 0, keys);
+}
+
+std::vector<ContentProviderEntry> ManualContentProvider::ListEntriesFilter(
std::optional<TitleType> title_type, std::optional<ContentRecordType> record_type,
std::optional<u64> title_id) const {
- std::vector<RegisteredCacheEntry> out;
- for (const auto& c : caches) {
- c->IterateAllMetadata<RegisteredCacheEntry>(
- out,
- [](const CNMT& c, const ContentRecord& r) {
- return RegisteredCacheEntry{c.GetTitleID(), r.type};
- },
- [&title_type, &record_type, &title_id](const CNMT& c, const ContentRecord& r) {
- if (title_type && *title_type != c.GetType())
- return false;
- if (record_type && *record_type != r.type)
- return false;
- if (title_id && *title_id != c.GetTitleID())
- return false;
- return true;
- });
+ std::vector<ContentProviderEntry> out;
+
+ for (const auto& entry : entries) {
+ const auto [e_title_type, e_content_type, e_title_id] = entry.first;
+ if ((title_type == std::nullopt || e_title_type == *title_type) &&
+ (record_type == std::nullopt || e_content_type == *record_type) &&
+ (title_id == std::nullopt || e_title_id == *title_id)) {
+ out.emplace_back(ContentProviderEntry{e_title_id, e_content_type});
+ }
}
std::sort(out.begin(), out.end());
diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h
index 3b77af4e0..ec9052653 100644
--- a/src/core/file_sys/registered_cache.h
+++ b/src/core/file_sys/registered_cache.h
@@ -21,12 +21,13 @@ class NSP;
class XCI;
enum class ContentRecordType : u8;
+enum class NCAContentType : u8;
enum class TitleType : u8;
struct ContentRecord;
using NcaID = std::array<u8, 0x10>;
-using RegisteredCacheParsingFunction = std::function<VirtualFile(const VirtualFile&, const NcaID&)>;
+using ContentProviderParsingFunction = std::function<VirtualFile(const VirtualFile&, const NcaID&)>;
using VfsCopyFunction = std::function<bool(const VirtualFile&, const VirtualFile&, size_t)>;
enum class InstallResult {
@@ -36,7 +37,7 @@ enum class InstallResult {
ErrorMetaFailed,
};
-struct RegisteredCacheEntry {
+struct ContentProviderEntry {
u64 title_id;
ContentRecordType type;
@@ -47,12 +48,46 @@ constexpr u64 GetUpdateTitleID(u64 base_title_id) {
return base_title_id | 0x800;
}
+ContentRecordType GetCRTypeFromNCAType(NCAContentType type);
+
// boost flat_map requires operator< for O(log(n)) lookups.
-bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs);
+bool operator<(const ContentProviderEntry& lhs, const ContentProviderEntry& rhs);
// std unique requires operator== to identify duplicates.
-bool operator==(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs);
-bool operator!=(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs);
+bool operator==(const ContentProviderEntry& lhs, const ContentProviderEntry& rhs);
+bool operator!=(const ContentProviderEntry& lhs, const ContentProviderEntry& rhs);
+
+class ContentProvider {
+public:
+ virtual ~ContentProvider();
+
+ virtual void Refresh() = 0;
+
+ virtual bool HasEntry(u64 title_id, ContentRecordType type) const = 0;
+ virtual 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;
+
+ virtual VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const = 0;
+ virtual 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;
+
+ virtual std::vector<ContentProviderEntry> ListEntries() const;
+
+ // If a parameter is not std::nullopt, it will be filtered for from all entries.
+ virtual std::vector<ContentProviderEntry> ListEntriesFilter(
+ std::optional<TitleType> title_type = {}, std::optional<ContentRecordType> record_type = {},
+ std::optional<u64> title_id = {}) const = 0;
+
+protected:
+ // A single instance of KeyManager to be used by GetEntry()
+ Core::Crypto::KeyManager keys;
+};
/*
* A class that catalogues NCAs in the registered directory structure.
@@ -67,39 +102,32 @@ bool operator!=(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs
* (This impl also supports substituting the nca dir for an nca file, as that's more convenient
* when 4GB splitting can be ignored.)
*/
-class RegisteredCache {
- friend class RegisteredCacheUnion;
-
+class RegisteredCache : public ContentProvider {
public:
// Parsing function defines the conversion from raw file to NCA. If there are other steps
// besides creating the NCA from the file (e.g. NAX0 on SD Card), that should go in a custom
// parsing function.
explicit RegisteredCache(VirtualDir dir,
- RegisteredCacheParsingFunction parsing_function =
+ ContentProviderParsingFunction parsing_function =
[](const VirtualFile& file, const NcaID& id) { return file; });
- ~RegisteredCache();
+ ~RegisteredCache() override;
- void Refresh();
+ void Refresh() override;
- bool HasEntry(u64 title_id, ContentRecordType type) const;
- bool HasEntry(RegisteredCacheEntry entry) const;
+ bool HasEntry(u64 title_id, ContentRecordType type) const override;
- std::optional<u32> GetEntryVersion(u64 title_id) const;
+ std::optional<u32> GetEntryVersion(u64 title_id) const override;
- VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const;
- VirtualFile GetEntryUnparsed(RegisteredCacheEntry entry) const;
+ VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const override;
- VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const;
- VirtualFile GetEntryRaw(RegisteredCacheEntry entry) const;
+ VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const override;
- std::unique_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const;
- std::unique_ptr<NCA> GetEntry(RegisteredCacheEntry entry) const;
+ std::unique_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const override;
- std::vector<RegisteredCacheEntry> ListEntries() const;
// If a parameter is not std::nullopt, it will be filtered for from all entries.
- std::vector<RegisteredCacheEntry> ListEntriesFilter(
+ std::vector<ContentProviderEntry> ListEntriesFilter(
std::optional<TitleType> title_type = {}, std::optional<ContentRecordType> record_type = {},
- std::optional<u64> title_id = {}) const;
+ std::optional<u64> title_id = {}) const override;
// Raw copies all the ncas from the xci/nsp to the csache. Does some quick checks to make sure
// there is a meta NCA and all of them are accessible.
@@ -131,46 +159,70 @@ private:
bool RawInstallYuzuMeta(const CNMT& cnmt);
VirtualDir dir;
- RegisteredCacheParsingFunction parser;
- Core::Crypto::KeyManager keys;
+ ContentProviderParsingFunction parser;
// maps tid -> NcaID of meta
- boost::container::flat_map<u64, NcaID> meta_id;
+ std::map<u64, NcaID> meta_id;
// maps tid -> meta
- boost::container::flat_map<u64, CNMT> meta;
+ std::map<u64, CNMT> meta;
// maps tid -> meta for CNMT in yuzu_meta
- boost::container::flat_map<u64, CNMT> yuzu_meta;
+ std::map<u64, CNMT> yuzu_meta;
};
-// Combines multiple RegisteredCaches (i.e. SysNAND, UserNAND, SDMC) into one interface.
-class RegisteredCacheUnion {
-public:
- explicit RegisteredCacheUnion(std::vector<RegisteredCache*> caches);
-
- void Refresh();
-
- bool HasEntry(u64 title_id, ContentRecordType type) const;
- bool HasEntry(RegisteredCacheEntry entry) const;
-
- std::optional<u32> GetEntryVersion(u64 title_id) const;
-
- VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const;
- VirtualFile GetEntryUnparsed(RegisteredCacheEntry entry) const;
-
- VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const;
- VirtualFile GetEntryRaw(RegisteredCacheEntry entry) const;
-
- std::unique_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const;
- std::unique_ptr<NCA> GetEntry(RegisteredCacheEntry entry) const;
+enum class ContentProviderUnionSlot {
+ SysNAND, ///< System NAND
+ UserNAND, ///< User NAND
+ SDMC, ///< SD Card
+ FrontendManual, ///< Frontend-defined game list or similar
+};
- std::vector<RegisteredCacheEntry> ListEntries() const;
- // If a parameter is not std::nullopt, it will be filtered for from all entries.
- std::vector<RegisteredCacheEntry> ListEntriesFilter(
+// Combines multiple ContentProvider(s) (i.e. SysNAND, UserNAND, SDMC) into one interface.
+class ContentProviderUnion : public ContentProvider {
+public:
+ ~ContentProviderUnion() override;
+
+ void SetSlot(ContentProviderUnionSlot slot, ContentProvider* provider);
+ void ClearSlot(ContentProviderUnionSlot slot);
+
+ void Refresh() override;
+ bool HasEntry(u64 title_id, ContentRecordType type) const override;
+ std::optional<u32> GetEntryVersion(u64 title_id) const override;
+ VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const override;
+ VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const override;
+ std::unique_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const override;
+ std::vector<ContentProviderEntry> ListEntriesFilter(
+ std::optional<TitleType> title_type, std::optional<ContentRecordType> record_type,
+ std::optional<u64> title_id) const override;
+
+ std::vector<std::pair<ContentProviderUnionSlot, ContentProviderEntry>> ListEntriesFilterOrigin(
+ std::optional<ContentProviderUnionSlot> origin = {},
std::optional<TitleType> title_type = {}, std::optional<ContentRecordType> record_type = {},
std::optional<u64> title_id = {}) const;
private:
- std::vector<RegisteredCache*> caches;
+ std::map<ContentProviderUnionSlot, ContentProvider*> providers;
+};
+
+class ManualContentProvider : public ContentProvider {
+public:
+ ~ManualContentProvider() override;
+
+ void AddEntry(TitleType title_type, ContentRecordType content_type, u64 title_id,
+ VirtualFile file);
+ void ClearAllEntries();
+
+ void Refresh() override;
+ bool HasEntry(u64 title_id, ContentRecordType type) const override;
+ std::optional<u32> GetEntryVersion(u64 title_id) const override;
+ VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const override;
+ VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const override;
+ std::unique_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const override;
+ std::vector<ContentProviderEntry> ListEntriesFilter(
+ std::optional<TitleType> title_type, std::optional<ContentRecordType> record_type,
+ std::optional<u64> title_id) const override;
+
+private:
+ std::map<std::tuple<TitleType, ContentRecordType, u64>, VirtualFile> entries;
};
} // namespace FileSys
diff --git a/src/core/file_sys/romfs_factory.cpp b/src/core/file_sys/romfs_factory.cpp
index 6ad1e4f86..b2ccb2926 100644
--- a/src/core/file_sys/romfs_factory.cpp
+++ b/src/core/file_sys/romfs_factory.cpp
@@ -48,7 +48,7 @@ ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage, Conte
switch (storage) {
case StorageId::None:
- res = Service::FileSystem::GetUnionContents().GetEntry(title_id, type);
+ res = Core::System::GetInstance().GetContentProvider().GetEntry(title_id, type);
break;
case StorageId::NandSystem:
res = Service::FileSystem::GetSystemNANDContents()->GetEntry(title_id, type);
diff --git a/src/core/file_sys/savedata_factory.cpp b/src/core/file_sys/savedata_factory.cpp
index 1913dc956..7974b031d 100644
--- a/src/core/file_sys/savedata_factory.cpp
+++ b/src/core/file_sys/savedata_factory.cpp
@@ -16,8 +16,10 @@ namespace FileSys {
constexpr char SAVE_DATA_SIZE_FILENAME[] = ".yuzu_save_size";
std::string SaveDataDescriptor::DebugInfo() const {
- return fmt::format("[type={:02X}, title_id={:016X}, user_id={:016X}{:016X}, save_id={:016X}]",
- static_cast<u8>(type), title_id, user_id[1], user_id[0], save_id);
+ return fmt::format("[type={:02X}, title_id={:016X}, user_id={:016X}{:016X}, save_id={:016X}, "
+ "rank={}, index={}]",
+ static_cast<u8>(type), title_id, user_id[1], user_id[0], save_id,
+ static_cast<u8>(rank), index);
}
SaveDataFactory::SaveDataFactory(VirtualDir save_directory) : dir(std::move(save_directory)) {
@@ -28,7 +30,7 @@ SaveDataFactory::SaveDataFactory(VirtualDir save_directory) : dir(std::move(save
SaveDataFactory::~SaveDataFactory() = default;
-ResultVal<VirtualDir> SaveDataFactory::Open(SaveDataSpaceId space, SaveDataDescriptor meta) {
+ResultVal<VirtualDir> SaveDataFactory::Open(SaveDataSpaceId space, const SaveDataDescriptor& meta) {
if (meta.type == SaveDataType::SystemSaveData || meta.type == SaveDataType::SaveData) {
if (meta.zero_1 != 0) {
LOG_WARNING(Service_FS,
diff --git a/src/core/file_sys/savedata_factory.h b/src/core/file_sys/savedata_factory.h
index 3a1caf292..b73654571 100644
--- a/src/core/file_sys/savedata_factory.h
+++ b/src/core/file_sys/savedata_factory.h
@@ -32,12 +32,19 @@ enum class SaveDataType : u8 {
CacheStorage = 5,
};
+enum class SaveDataRank : u8 {
+ Primary,
+ Secondary,
+};
+
struct SaveDataDescriptor {
u64_le title_id;
u128 user_id;
u64_le save_id;
SaveDataType type;
- INSERT_PADDING_BYTES(7);
+ SaveDataRank rank;
+ u16_le index;
+ INSERT_PADDING_BYTES(4);
u64_le zero_1;
u64_le zero_2;
u64_le zero_3;
@@ -57,7 +64,7 @@ public:
explicit SaveDataFactory(VirtualDir dir);
~SaveDataFactory();
- ResultVal<VirtualDir> Open(SaveDataSpaceId space, SaveDataDescriptor meta);
+ ResultVal<VirtualDir> Open(SaveDataSpaceId space, const SaveDataDescriptor& meta);
VirtualDir GetSaveDataSpaceDirectory(SaveDataSpaceId space) const;
diff --git a/src/core/file_sys/submission_package.cpp b/src/core/file_sys/submission_package.cpp
index e1a4210db..d0428a457 100644
--- a/src/core/file_sys/submission_package.cpp
+++ b/src/core/file_sys/submission_package.cpp
@@ -143,11 +143,12 @@ std::multimap<u64, std::shared_ptr<NCA>> NSP::GetNCAsByTitleID() const {
return out;
}
-std::map<u64, std::map<ContentRecordType, std::shared_ptr<NCA>>> NSP::GetNCAs() const {
+std::map<u64, std::map<std::pair<TitleType, ContentRecordType>, std::shared_ptr<NCA>>>
+NSP::GetNCAs() const {
return ncas;
}
-std::shared_ptr<NCA> NSP::GetNCA(u64 title_id, ContentRecordType type) const {
+std::shared_ptr<NCA> NSP::GetNCA(u64 title_id, ContentRecordType type, TitleType title_type) const {
if (extracted)
LOG_WARNING(Service_FS, "called on an NSP that is of type extracted.");
@@ -155,14 +156,14 @@ std::shared_ptr<NCA> NSP::GetNCA(u64 title_id, ContentRecordType type) const {
if (title_id_iter == ncas.end())
return nullptr;
- const auto type_iter = title_id_iter->second.find(type);
+ const auto type_iter = title_id_iter->second.find({title_type, type});
if (type_iter == title_id_iter->second.end())
return nullptr;
return type_iter->second;
}
-VirtualFile NSP::GetNCAFile(u64 title_id, ContentRecordType type) const {
+VirtualFile NSP::GetNCAFile(u64 title_id, ContentRecordType type, TitleType title_type) const {
if (extracted)
LOG_WARNING(Service_FS, "called on an NSP that is of type extracted.");
const auto nca = GetNCA(title_id, type);
@@ -234,16 +235,18 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) {
const auto section0 = nca->GetSubdirectories()[0];
for (const auto& inner_file : section0->GetFiles()) {
- if (inner_file->GetExtension() != "cnmt")
+ if (inner_file->GetExtension() != "cnmt") {
continue;
+ }
const CNMT cnmt(inner_file);
auto& ncas_title = ncas[cnmt.GetTitleID()];
- ncas_title[ContentRecordType::Meta] = nca;
+ ncas_title[{cnmt.GetType(), ContentRecordType::Meta}] = nca;
for (const auto& rec : cnmt.GetContentRecords()) {
- const auto id_string = Common::HexArrayToString(rec.nca_id, false);
- const auto next_file = pfs->GetFile(fmt::format("{}.nca", id_string));
+ const auto id_string = Common::HexToString(rec.nca_id, false);
+ auto next_file = pfs->GetFile(fmt::format("{}.nca", id_string));
+
if (next_file == nullptr) {
LOG_WARNING(Service_FS,
"NCA with ID {}.nca is listed in content metadata, but cannot "
@@ -252,13 +255,14 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) {
continue;
}
- auto next_nca = std::make_shared<NCA>(next_file, nullptr, 0, keys);
- if (next_nca->GetType() == NCAContentType::Program)
+ auto next_nca = std::make_shared<NCA>(std::move(next_file), nullptr, 0, keys);
+ if (next_nca->GetType() == NCAContentType::Program) {
program_status[cnmt.GetTitleID()] = next_nca->GetStatus();
+ }
if (next_nca->GetStatus() == Loader::ResultStatus::Success ||
(next_nca->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS &&
(cnmt.GetTitleID() & 0x800) != 0)) {
- ncas_title[rec.type] = std::move(next_nca);
+ ncas_title[{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 9a28ed5bb..ee9b6ce17 100644
--- a/src/core/file_sys/submission_package.h
+++ b/src/core/file_sys/submission_package.h
@@ -42,9 +42,12 @@ public:
// Type 0 Only (Collection of NCAs + Certificate + Ticket + Meta XML)
std::vector<std::shared_ptr<NCA>> GetNCAsCollapsed() const;
std::multimap<u64, std::shared_ptr<NCA>> GetNCAsByTitleID() const;
- std::map<u64, std::map<ContentRecordType, std::shared_ptr<NCA>>> GetNCAs() const;
- std::shared_ptr<NCA> GetNCA(u64 title_id, ContentRecordType type) const;
- VirtualFile GetNCAFile(u64 title_id, ContentRecordType type) const;
+ std::map<u64, std::map<std::pair<TitleType, ContentRecordType>, std::shared_ptr<NCA>>> GetNCAs()
+ const;
+ std::shared_ptr<NCA> GetNCA(u64 title_id, ContentRecordType type,
+ TitleType title_type = TitleType::Application) const;
+ VirtualFile GetNCAFile(u64 title_id, ContentRecordType type,
+ TitleType title_type = TitleType::Application) const;
std::vector<Core::Crypto::Key128> GetTitlekey() const;
std::vector<VirtualFile> GetFiles() const override;
@@ -67,7 +70,7 @@ private:
std::shared_ptr<PartitionFilesystem> pfs;
// Map title id -> {map type -> NCA}
- std::map<u64, std::map<ContentRecordType, std::shared_ptr<NCA>>> ncas;
+ std::map<u64, std::map<std::pair<TitleType, ContentRecordType>, std::shared_ptr<NCA>>> ncas;
std::vector<VirtualFile> ticket_files;
Core::Crypto::KeyManager keys;
diff --git a/src/core/file_sys/system_archive/system_archive.cpp b/src/core/file_sys/system_archive/system_archive.cpp
index e3e79f40a..c9722ed77 100644
--- a/src/core/file_sys/system_archive/system_archive.cpp
+++ b/src/core/file_sys/system_archive/system_archive.cpp
@@ -6,6 +6,7 @@
#include "core/file_sys/romfs.h"
#include "core/file_sys/system_archive/ng_word.h"
#include "core/file_sys/system_archive/system_archive.h"
+#include "core/file_sys/system_archive/system_version.h"
namespace FileSys::SystemArchive {
@@ -30,7 +31,7 @@ constexpr std::array<SystemArchiveDescriptor, SYSTEM_ARCHIVE_COUNT> SYSTEM_ARCHI
{0x0100000000000806, "NgWord", &NgWord1},
{0x0100000000000807, "SsidList", nullptr},
{0x0100000000000808, "Dictionary", nullptr},
- {0x0100000000000809, "SystemVersion", nullptr},
+ {0x0100000000000809, "SystemVersion", &SystemVersion},
{0x010000000000080A, "AvatarImage", nullptr},
{0x010000000000080B, "LocalNews", nullptr},
{0x010000000000080C, "Eula", nullptr},
diff --git a/src/core/file_sys/system_archive/system_version.cpp b/src/core/file_sys/system_archive/system_version.cpp
new file mode 100644
index 000000000..6e22f97b0
--- /dev/null
+++ b/src/core/file_sys/system_archive/system_version.cpp
@@ -0,0 +1,52 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/file_sys/system_archive/system_version.h"
+#include "core/file_sys/vfs_vector.h"
+
+namespace FileSys::SystemArchive {
+
+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 = 5;
+constexpr u8 VERSION_MINOR = 1;
+constexpr u8 VERSION_MICRO = 0;
+
+constexpr u8 REVISION_MAJOR = 3;
+constexpr u8 REVISION_MINOR = 0;
+
+constexpr char PLATFORM_STRING[] = "NX";
+constexpr char VERSION_HASH[] = "23f9df53e25709d756e0c76effcb2473bd3447dd";
+constexpr char DISPLAY_VERSION[] = "5.1.0";
+constexpr char DISPLAY_TITLE[] = "NintendoSDK Firmware for NX 5.1.0-3.0";
+
+} // namespace SystemVersionData
+
+std::string GetLongDisplayVersion() {
+ return SystemVersionData::DISPLAY_TITLE;
+}
+
+VirtualDir SystemVersion() {
+ VirtualFile file = std::make_shared<VectorVfsFile>(std::vector<u8>(0x100), "file");
+ file->WriteObject(SystemVersionData::VERSION_MAJOR, 0);
+ file->WriteObject(SystemVersionData::VERSION_MINOR, 1);
+ file->WriteObject(SystemVersionData::VERSION_MICRO, 2);
+ file->WriteObject(SystemVersionData::REVISION_MAJOR, 4);
+ file->WriteObject(SystemVersionData::REVISION_MINOR, 5);
+ file->WriteArray(SystemVersionData::PLATFORM_STRING,
+ std::min<u64>(sizeof(SystemVersionData::PLATFORM_STRING), 0x20ULL), 0x8);
+ file->WriteArray(SystemVersionData::VERSION_HASH,
+ std::min<u64>(sizeof(SystemVersionData::VERSION_HASH), 0x40ULL), 0x28);
+ file->WriteArray(SystemVersionData::DISPLAY_VERSION,
+ std::min<u64>(sizeof(SystemVersionData::DISPLAY_VERSION), 0x18ULL), 0x68);
+ file->WriteArray(SystemVersionData::DISPLAY_TITLE,
+ std::min<u64>(sizeof(SystemVersionData::DISPLAY_TITLE), 0x80ULL), 0x80);
+ return std::make_shared<VectorVfsDirectory>(std::vector<VirtualFile>{file},
+ std::vector<VirtualDir>{}, "data");
+}
+
+} // namespace FileSys::SystemArchive
diff --git a/src/core/file_sys/system_archive/system_version.h b/src/core/file_sys/system_archive/system_version.h
new file mode 100644
index 000000000..deed79b26
--- /dev/null
+++ b/src/core/file_sys/system_archive/system_version.h
@@ -0,0 +1,16 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <string>
+#include "core/file_sys/vfs_types.h"
+
+namespace FileSys::SystemArchive {
+
+std::string GetLongDisplayVersion();
+
+VirtualDir SystemVersion();
+
+} // namespace FileSys::SystemArchive
diff --git a/src/core/file_sys/xts_archive.cpp b/src/core/file_sys/xts_archive.cpp
index eec51c64e..4bc5cb2ee 100644
--- a/src/core/file_sys/xts_archive.cpp
+++ b/src/core/file_sys/xts_archive.cpp
@@ -66,7 +66,7 @@ NAX::NAX(VirtualFile file_, std::array<u8, 0x10> nca_id)
Core::Crypto::SHA256Hash hash{};
mbedtls_sha256(nca_id.data(), nca_id.size(), hash.data(), 0);
status = Parse(fmt::format("/registered/000000{:02X}/{}.nca", hash[0],
- Common::HexArrayToString(nca_id, false)));
+ Common::HexToString(nca_id, false)));
}
NAX::~NAX() = default;
diff --git a/src/core/frontend/applets/error.cpp b/src/core/frontend/applets/error.cpp
new file mode 100644
index 000000000..4002a9211
--- /dev/null
+++ b/src/core/frontend/applets/error.cpp
@@ -0,0 +1,34 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/frontend/applets/error.h"
+
+namespace Core::Frontend {
+
+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);
+}
+
+void DefaultErrorApplet::ShowErrorWithTimestamp(ResultCode error, std::chrono::seconds time,
+ std::function<void()> finished) const {
+ 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());
+}
+
+void DefaultErrorApplet::ShowCustomErrorText(ResultCode error, std::string main_text,
+ std::string detail_text,
+ 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);
+ LOG_CRITICAL(Service_Fatal, " Main Text: {}", main_text);
+ LOG_CRITICAL(Service_Fatal, " Detail Text: {}", detail_text);
+}
+
+} // namespace Core::Frontend
diff --git a/src/core/frontend/applets/error.h b/src/core/frontend/applets/error.h
new file mode 100644
index 000000000..699df940d
--- /dev/null
+++ b/src/core/frontend/applets/error.h
@@ -0,0 +1,37 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <chrono>
+#include <functional>
+
+#include "core/hle/result.h"
+
+namespace Core::Frontend {
+
+class ErrorApplet {
+public:
+ virtual ~ErrorApplet();
+
+ virtual void ShowError(ResultCode error, std::function<void()> finished) const = 0;
+
+ virtual void ShowErrorWithTimestamp(ResultCode error, std::chrono::seconds time,
+ std::function<void()> finished) const = 0;
+
+ virtual void ShowCustomErrorText(ResultCode error, std::string dialog_text,
+ std::string fullscreen_text,
+ std::function<void()> finished) const = 0;
+};
+
+class DefaultErrorApplet final : public ErrorApplet {
+public:
+ void ShowError(ResultCode error, std::function<void()> finished) const override;
+ void ShowErrorWithTimestamp(ResultCode error, std::chrono::seconds time,
+ std::function<void()> finished) const override;
+ void ShowCustomErrorText(ResultCode error, std::string main_text, std::string detail_text,
+ std::function<void()> finished) const override;
+};
+
+} // namespace Core::Frontend
diff --git a/src/core/frontend/applets/general_frontend.cpp b/src/core/frontend/applets/general_frontend.cpp
new file mode 100644
index 000000000..b974f2289
--- /dev/null
+++ b/src/core/frontend/applets/general_frontend.cpp
@@ -0,0 +1,27 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/logging/log.h"
+#include "core/frontend/applets/general_frontend.h"
+
+namespace Core::Frontend {
+
+PhotoViewerApplet::~PhotoViewerApplet() = default;
+
+DefaultPhotoViewerApplet::~DefaultPhotoViewerApplet() {}
+
+void DefaultPhotoViewerApplet::ShowPhotosForApplication(u64 title_id,
+ std::function<void()> finished) const {
+ LOG_INFO(Service_AM,
+ "Application requested frontend to display stored photos for title_id={:016X}",
+ title_id);
+ finished();
+}
+
+void DefaultPhotoViewerApplet::ShowAllPhotos(std::function<void()> finished) const {
+ LOG_INFO(Service_AM, "Application requested frontend to display all stored photos.");
+ finished();
+}
+
+} // namespace Core::Frontend
diff --git a/src/core/frontend/applets/general_frontend.h b/src/core/frontend/applets/general_frontend.h
new file mode 100644
index 000000000..d4506c999
--- /dev/null
+++ b/src/core/frontend/applets/general_frontend.h
@@ -0,0 +1,28 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <functional>
+#include "common/common_types.h"
+
+namespace Core::Frontend {
+
+class PhotoViewerApplet {
+public:
+ virtual ~PhotoViewerApplet();
+
+ virtual void ShowPhotosForApplication(u64 title_id, std::function<void()> finished) const = 0;
+ virtual void ShowAllPhotos(std::function<void()> finished) const = 0;
+};
+
+class DefaultPhotoViewerApplet final : public PhotoViewerApplet {
+public:
+ ~DefaultPhotoViewerApplet() override;
+
+ void ShowPhotosForApplication(u64 title_id, std::function<void()> finished) const override;
+ void ShowAllPhotos(std::function<void()> finished) const override;
+};
+
+} // namespace Core::Frontend
diff --git a/src/core/frontend/applets/profile_select.cpp b/src/core/frontend/applets/profile_select.cpp
index fbf5f2a9e..4df3574d2 100644
--- a/src/core/frontend/applets/profile_select.cpp
+++ b/src/core/frontend/applets/profile_select.cpp
@@ -3,6 +3,7 @@
// Refer to the license.txt file included.
#include "core/frontend/applets/profile_select.h"
+#include "core/hle/service/acc/profile_manager.h"
#include "core/settings.h"
namespace Core::Frontend {
@@ -10,9 +11,9 @@ namespace Core::Frontend {
ProfileSelectApplet::~ProfileSelectApplet() = default;
void DefaultProfileSelectApplet::SelectProfile(
- std::function<void(std::optional<Service::Account::UUID>)> callback) const {
+ std::function<void(std::optional<Common::UUID>)> callback) const {
Service::Account::ProfileManager manager;
- callback(manager.GetUser(Settings::values.current_user).value_or(Service::Account::UUID{}));
+ callback(manager.GetUser(Settings::values.current_user).value_or(Common::UUID{}));
LOG_INFO(Service_ACC, "called, selecting current user instead of prompting...");
}
diff --git a/src/core/frontend/applets/profile_select.h b/src/core/frontend/applets/profile_select.h
index fc8f7ae94..3506b9885 100644
--- a/src/core/frontend/applets/profile_select.h
+++ b/src/core/frontend/applets/profile_select.h
@@ -6,7 +6,7 @@
#include <functional>
#include <optional>
-#include "core/hle/service/acc/profile_manager.h"
+#include "common/uuid.h"
namespace Core::Frontend {
@@ -14,14 +14,12 @@ class ProfileSelectApplet {
public:
virtual ~ProfileSelectApplet();
- virtual void SelectProfile(
- std::function<void(std::optional<Service::Account::UUID>)> callback) const = 0;
+ virtual void SelectProfile(std::function<void(std::optional<Common::UUID>)> callback) const = 0;
};
class DefaultProfileSelectApplet final : public ProfileSelectApplet {
public:
- void SelectProfile(
- std::function<void(std::optional<Service::Account::UUID>)> callback) const override;
+ void SelectProfile(std::function<void(std::optional<Common::UUID>)> callback) const override;
};
} // namespace Core::Frontend
diff --git a/src/core/frontend/emu_window.cpp b/src/core/frontend/emu_window.cpp
index e29afd630..eda466a5d 100644
--- a/src/core/frontend/emu_window.cpp
+++ b/src/core/frontend/emu_window.cpp
@@ -10,6 +10,8 @@
namespace Core::Frontend {
+GraphicsContext::~GraphicsContext() = default;
+
class EmuWindow::TouchState : public Input::Factory<Input::TouchDevice>,
public std::enable_shared_from_this<TouchState> {
public:
@@ -30,7 +32,7 @@ private:
explicit Device(std::weak_ptr<TouchState>&& touch_state) : touch_state(touch_state) {}
std::tuple<float, float, bool> GetStatus() const override {
if (auto state = touch_state.lock()) {
- std::lock_guard<std::mutex> guard(state->mutex);
+ std::lock_guard guard{state->mutex};
return std::make_tuple(state->touch_x, state->touch_y, state->touch_pressed);
}
return std::make_tuple(0.0f, 0.0f, false);
@@ -81,7 +83,7 @@ void EmuWindow::TouchPressed(unsigned framebuffer_x, unsigned framebuffer_y) {
if (!IsWithinTouchscreen(framebuffer_layout, framebuffer_x, framebuffer_y))
return;
- std::lock_guard<std::mutex> guard(touch_state->mutex);
+ 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) /
@@ -91,7 +93,7 @@ void EmuWindow::TouchPressed(unsigned framebuffer_x, unsigned framebuffer_y) {
}
void EmuWindow::TouchReleased() {
- std::lock_guard<std::mutex> guard(touch_state->mutex);
+ std::lock_guard guard{touch_state->mutex};
touch_state->touch_pressed = false;
touch_state->touch_x = 0;
touch_state->touch_y = 0;
diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h
index d0bcb4660..4a9912641 100644
--- a/src/core/frontend/emu_window.h
+++ b/src/core/frontend/emu_window.h
@@ -13,6 +13,25 @@
namespace Core::Frontend {
/**
+ * Represents a graphics context that can be used for background computation or drawing. If the
+ * graphics backend doesn't require the context, then the implementation of these methods can be
+ * stubs
+ */
+class GraphicsContext {
+public:
+ virtual ~GraphicsContext();
+
+ /// Makes the graphics context current for the caller thread
+ virtual void MakeCurrent() = 0;
+
+ /// Releases (dunno if this is the "right" word) the context from the caller thread
+ virtual void DoneCurrent() = 0;
+
+ /// Swap buffers to display the next frame
+ virtual void SwapBuffers() = 0;
+};
+
+/**
* Abstraction class used to provide an interface between emulation code and the frontend
* (e.g. SDL, QGLWidget, GLFW, etc...).
*
@@ -30,7 +49,7 @@ namespace Core::Frontend {
* - DO NOT TREAT THIS CLASS AS A GUI TOOLKIT ABSTRACTION LAYER. That's not what it is. Please
* re-read the upper points again and think about it if you don't see this.
*/
-class EmuWindow {
+class EmuWindow : public GraphicsContext {
public:
/// Data structure to store emuwindow configuration
struct WindowConfig {
@@ -40,17 +59,21 @@ public:
std::pair<unsigned, unsigned> min_client_area_size;
};
- /// Swap buffers to display the next frame
- virtual void SwapBuffers() = 0;
-
/// Polls window events
virtual void PollEvents() = 0;
- /// Makes the graphics context current for the caller thread
- virtual void MakeCurrent() = 0;
-
- /// Releases (dunno if this is the "right" word) the GLFW context from the caller thread
- virtual void DoneCurrent() = 0;
+ /**
+ * Returns a GraphicsContext that the frontend provides that is shared with the emu window. This
+ * context can be used from other threads for background graphics computation. If the frontend
+ * is using a graphics backend that doesn't need anything specific to run on a different thread,
+ * then it can use a stubbed implemenation for GraphicsContext.
+ *
+ * If the return value is null, then the core should assume that the frontend cannot provide a
+ * Shared Context
+ */
+ virtual std::unique_ptr<GraphicsContext> CreateSharedContext() const {
+ return nullptr;
+ }
/**
* Signal that a touch pressed event has occurred (e.g. mouse click pressed)
@@ -146,8 +169,7 @@ private:
* For the request to be honored, EmuWindow implementations will usually reimplement this
* function.
*/
- virtual void OnMinimalClientAreaChangeRequest(
- const std::pair<unsigned, unsigned>& minimal_size) {
+ virtual void OnMinimalClientAreaChangeRequest(std::pair<unsigned, unsigned>) {
// By default, ignore this request and do nothing.
}
diff --git a/src/core/frontend/framebuffer_layout.cpp b/src/core/frontend/framebuffer_layout.cpp
index a1357179f..d6d2cf3f0 100644
--- a/src/core/frontend/framebuffer_layout.cpp
+++ b/src/core/frontend/framebuffer_layout.cpp
@@ -20,7 +20,7 @@ static Common::Rectangle<T> MaxRectangle(Common::Rectangle<T> window_area,
static_cast<T>(std::round(scale * screen_aspect_ratio))};
}
-FramebufferLayout DefaultFrameLayout(unsigned width, unsigned height) {
+FramebufferLayout DefaultFrameLayout(u32 width, u32 height) {
ASSERT(width > 0);
ASSERT(height > 0);
// The drawing code needs at least somewhat valid values for both screens
@@ -29,22 +29,23 @@ FramebufferLayout DefaultFrameLayout(unsigned width, unsigned height) {
const float emulation_aspect_ratio{static_cast<float>(ScreenUndocked::Height) /
ScreenUndocked::Width};
- Common::Rectangle<unsigned> screen_window_area{0, 0, width, height};
- Common::Rectangle<unsigned> screen = MaxRectangle(screen_window_area, emulation_aspect_ratio);
+ const auto window_aspect_ratio = static_cast<float>(height) / width;
- float window_aspect_ratio = static_cast<float>(height) / width;
+ const Common::Rectangle<u32> screen_window_area{0, 0, width, height};
+ Common::Rectangle<u32> screen = MaxRectangle(screen_window_area, emulation_aspect_ratio);
if (window_aspect_ratio < emulation_aspect_ratio) {
screen = screen.TranslateX((screen_window_area.GetWidth() - screen.GetWidth()) / 2);
} else {
screen = screen.TranslateY((height - screen.GetHeight()) / 2);
}
+
res.screen = screen;
return res;
}
-FramebufferLayout FrameLayoutFromResolutionScale(u16 res_scale) {
- int width, height;
+FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale) {
+ u32 width, height;
if (Settings::values.use_docked_mode) {
width = ScreenDocked::WidthDocked * res_scale;
diff --git a/src/core/frontend/framebuffer_layout.h b/src/core/frontend/framebuffer_layout.h
index c2c63d08c..d2370adde 100644
--- a/src/core/frontend/framebuffer_layout.h
+++ b/src/core/frontend/framebuffer_layout.h
@@ -8,15 +8,22 @@
namespace Layout {
-enum ScreenUndocked : unsigned { Width = 1280, Height = 720 };
-enum ScreenDocked : unsigned { WidthDocked = 1920, HeightDocked = 1080 };
+enum ScreenUndocked : u32 {
+ Width = 1280,
+ Height = 720,
+};
+
+enum ScreenDocked : u32 {
+ WidthDocked = 1920,
+ HeightDocked = 1080,
+};
/// Describes the layout of the window framebuffer
struct FramebufferLayout {
- unsigned width{ScreenUndocked::Width};
- unsigned height{ScreenUndocked::Height};
+ u32 width{ScreenUndocked::Width};
+ u32 height{ScreenUndocked::Height};
- Common::Rectangle<unsigned> screen;
+ Common::Rectangle<u32> screen;
/**
* Returns the ration of pixel size of the screen, compared to the native size of the undocked
@@ -33,12 +40,12 @@ struct FramebufferLayout {
* @param height Window framebuffer height in pixels
* @return Newly created FramebufferLayout object with default screen regions initialized
*/
-FramebufferLayout DefaultFrameLayout(unsigned width, unsigned height);
+FramebufferLayout DefaultFrameLayout(u32 width, u32 height);
/**
* Convenience method to get frame layout by resolution scale
* @param res_scale resolution scale factor
*/
-FramebufferLayout FrameLayoutFromResolutionScale(u16 res_scale);
+FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale);
} // namespace Layout
diff --git a/src/core/gdbstub/gdbstub.cpp b/src/core/gdbstub/gdbstub.cpp
index dafb32aae..afa812598 100644
--- a/src/core/gdbstub/gdbstub.cpp
+++ b/src/core/gdbstub/gdbstub.cpp
@@ -1030,7 +1030,7 @@ static void Step() {
/// Tell the CPU if we hit a memory breakpoint.
bool IsMemoryBreak() {
- if (IsConnected()) {
+ if (!IsConnected()) {
return false;
}
diff --git a/src/core/hle/ipc.h b/src/core/hle/ipc.h
index 455d1f346..fae54bcc7 100644
--- a/src/core/hle/ipc.h
+++ b/src/core/hle/ipc.h
@@ -39,10 +39,10 @@ struct CommandHeader {
union {
u32_le raw_low;
BitField<0, 16, CommandType> type;
- BitField<16, 4, u32_le> num_buf_x_descriptors;
- BitField<20, 4, u32_le> num_buf_a_descriptors;
- BitField<24, 4, u32_le> num_buf_b_descriptors;
- BitField<28, 4, u32_le> num_buf_w_descriptors;
+ BitField<16, 4, u32> num_buf_x_descriptors;
+ BitField<20, 4, u32> num_buf_a_descriptors;
+ BitField<24, 4, u32> num_buf_b_descriptors;
+ BitField<28, 4, u32> num_buf_w_descriptors;
};
enum class BufferDescriptorCFlag : u32 {
@@ -53,28 +53,28 @@ struct CommandHeader {
union {
u32_le raw_high;
- BitField<0, 10, u32_le> data_size;
+ BitField<0, 10, u32> data_size;
BitField<10, 4, BufferDescriptorCFlag> buf_c_descriptor_flags;
- BitField<31, 1, u32_le> enable_handle_descriptor;
+ BitField<31, 1, u32> enable_handle_descriptor;
};
};
static_assert(sizeof(CommandHeader) == 8, "CommandHeader size is incorrect");
union HandleDescriptorHeader {
u32_le raw_high;
- BitField<0, 1, u32_le> send_current_pid;
- BitField<1, 4, u32_le> num_handles_to_copy;
- BitField<5, 4, u32_le> num_handles_to_move;
+ BitField<0, 1, u32> send_current_pid;
+ BitField<1, 4, u32> num_handles_to_copy;
+ BitField<5, 4, u32> num_handles_to_move;
};
static_assert(sizeof(HandleDescriptorHeader) == 4, "HandleDescriptorHeader size is incorrect");
struct BufferDescriptorX {
union {
- BitField<0, 6, u32_le> counter_bits_0_5;
- BitField<6, 3, u32_le> address_bits_36_38;
- BitField<9, 3, u32_le> counter_bits_9_11;
- BitField<12, 4, u32_le> address_bits_32_35;
- BitField<16, 16, u32_le> size;
+ BitField<0, 6, u32> counter_bits_0_5;
+ BitField<6, 3, u32> address_bits_36_38;
+ BitField<9, 3, u32> counter_bits_9_11;
+ BitField<12, 4, u32> address_bits_32_35;
+ BitField<16, 16, u32> size;
};
u32_le address_bits_0_31;
@@ -103,10 +103,10 @@ struct BufferDescriptorABW {
u32_le address_bits_0_31;
union {
- BitField<0, 2, u32_le> flags;
- BitField<2, 3, u32_le> address_bits_36_38;
- BitField<24, 4, u32_le> size_bits_32_35;
- BitField<28, 4, u32_le> address_bits_32_35;
+ BitField<0, 2, u32> flags;
+ BitField<2, 3, u32> address_bits_36_38;
+ BitField<24, 4, u32> size_bits_32_35;
+ BitField<28, 4, u32> address_bits_32_35;
};
VAddr Address() const {
@@ -128,8 +128,8 @@ struct BufferDescriptorC {
u32_le address_bits_0_31;
union {
- BitField<0, 16, u32_le> address_bits_32_47;
- BitField<16, 16, u32_le> size;
+ BitField<0, 16, u32> address_bits_32_47;
+ BitField<16, 16, u32> size;
};
VAddr Address() const {
@@ -167,8 +167,8 @@ struct DomainMessageHeader {
struct {
union {
BitField<0, 8, CommandType> command;
- BitField<8, 8, u32_le> input_object_count;
- BitField<16, 16, u32_le> size;
+ BitField<8, 8, u32> input_object_count;
+ BitField<16, 16, u32> size;
};
u32_le object_id;
INSERT_PADDING_WORDS(2);
diff --git a/src/core/hle/ipc_helpers.h b/src/core/hle/ipc_helpers.h
index 90f276ee8..5bb139483 100644
--- a/src/core/hle/ipc_helpers.h
+++ b/src/core/hle/ipc_helpers.h
@@ -19,9 +19,12 @@
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/server_session.h"
+#include "core/hle/result.h"
namespace IPC {
+constexpr ResultCode ERR_REMOTE_PROCESS_DEAD{ErrorModule::HIPC, 301};
+
class RequestHelperBase {
protected:
Kernel::HLERequestContext* context = nullptr;
@@ -136,10 +139,8 @@ public:
context->AddDomainObject(std::move(iface));
} else {
auto& kernel = Core::System::GetInstance().Kernel();
- auto sessions =
+ auto [server, client] =
Kernel::ServerSession::CreateSessionPair(kernel, iface->GetServiceName());
- auto server = std::get<Kernel::SharedPtr<Kernel::ServerSession>>(sessions);
- auto client = std::get<Kernel::SharedPtr<Kernel::ClientSession>>(sessions);
iface->ClientConnected(server);
context->AddMoveObject(std::move(client));
}
@@ -272,6 +273,20 @@ inline void ResponseBuilder::Push(u64 value) {
}
template <>
+inline void ResponseBuilder::Push(float value) {
+ u32 integral;
+ std::memcpy(&integral, &value, sizeof(u32));
+ Push(integral);
+}
+
+template <>
+inline void ResponseBuilder::Push(double value) {
+ u64 integral;
+ std::memcpy(&integral, &value, sizeof(u64));
+ Push(integral);
+}
+
+template <>
inline void ResponseBuilder::Push(bool value) {
Push(static_cast<u8>(value));
}
@@ -350,7 +365,7 @@ public:
template <class T>
std::shared_ptr<T> PopIpcInterface() {
ASSERT(context->Session()->IsDomain());
- ASSERT(context->GetDomainMessageHeader()->input_object_count > 0);
+ ASSERT(context->GetDomainMessageHeader().input_object_count > 0);
return context->GetDomainRequestHandler<T>(Pop<u32>() - 1);
}
};
@@ -362,6 +377,11 @@ inline u32 RequestParser::Pop() {
return cmdbuf[index++];
}
+template <>
+inline s32 RequestParser::Pop() {
+ return static_cast<s32>(Pop<u32>());
+}
+
template <typename T>
void RequestParser::PopRaw(T& value) {
std::memcpy(&value, cmdbuf + index, sizeof(T));
@@ -393,11 +413,37 @@ inline u64 RequestParser::Pop() {
}
template <>
+inline s8 RequestParser::Pop() {
+ return static_cast<s8>(Pop<u8>());
+}
+
+template <>
+inline s16 RequestParser::Pop() {
+ return static_cast<s16>(Pop<u16>());
+}
+
+template <>
inline s64 RequestParser::Pop() {
return static_cast<s64>(Pop<u64>());
}
template <>
+inline float RequestParser::Pop() {
+ const u32 value = Pop<u32>();
+ float real;
+ std::memcpy(&real, &value, sizeof(real));
+ return real;
+}
+
+template <>
+inline double RequestParser::Pop() {
+ const u64 value = Pop<u64>();
+ double real;
+ std::memcpy(&real, &value, sizeof(real));
+ return real;
+}
+
+template <>
inline bool RequestParser::Pop() {
return Pop<u8>() != 0;
}
diff --git a/src/core/hle/kernel/address_arbiter.cpp b/src/core/hle/kernel/address_arbiter.cpp
index 9780a7849..c8842410b 100644
--- a/src/core/hle/kernel/address_arbiter.cpp
+++ b/src/core/hle/kernel/address_arbiter.cpp
@@ -26,7 +26,7 @@ void WakeThreads(const std::vector<SharedPtr<Thread>>& waiting_threads, s32 num_
// them all.
std::size_t last = waiting_threads.size();
if (num_to_wake > 0) {
- last = num_to_wake;
+ last = std::min(last, static_cast<std::size_t>(num_to_wake));
}
// Signal the waiting threads.
@@ -42,7 +42,21 @@ void WakeThreads(const std::vector<SharedPtr<Thread>>& waiting_threads, s32 num_
AddressArbiter::AddressArbiter(Core::System& system) : system{system} {}
AddressArbiter::~AddressArbiter() = default;
-ResultCode AddressArbiter::SignalToAddress(VAddr address, s32 num_to_wake) {
+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) {
const std::vector<SharedPtr<Thread>> waiting_threads = GetThreadsWaitingOnAddress(address);
WakeThreads(waiting_threads, num_to_wake);
return RESULT_SUCCESS;
@@ -60,7 +74,7 @@ ResultCode AddressArbiter::IncrementAndSignalToAddressIfEqual(VAddr address, s32
}
Memory::Write32(address, static_cast<u32>(value + 1));
- return SignalToAddress(address, num_to_wake);
+ return SignalToAddressOnly(address, num_to_wake);
}
ResultCode AddressArbiter::ModifyByWaitingCountAndSignalToAddressIfEqual(VAddr address, s32 value,
@@ -76,9 +90,9 @@ ResultCode AddressArbiter::ModifyByWaitingCountAndSignalToAddressIfEqual(VAddr a
// Determine the modified value depending on the waiting count.
s32 updated_value;
if (waiting_threads.empty()) {
- updated_value = value - 1;
- } else if (num_to_wake <= 0 || waiting_threads.size() <= static_cast<u32>(num_to_wake)) {
updated_value = value + 1;
+ } else if (num_to_wake <= 0 || waiting_threads.size() <= static_cast<u32>(num_to_wake)) {
+ updated_value = value - 1;
} else {
updated_value = value;
}
@@ -92,6 +106,20 @@ ResultCode AddressArbiter::ModifyByWaitingCountAndSignalToAddressIfEqual(VAddr a
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) {
// Ensure that we can read the address.
@@ -113,7 +141,7 @@ ResultCode AddressArbiter::WaitForAddressIfLessThan(VAddr address, s32 value, s6
return RESULT_TIMEOUT;
}
- return WaitForAddress(address, timeout);
+ return WaitForAddressImpl(address, timeout);
}
ResultCode AddressArbiter::WaitForAddressIfEqual(VAddr address, s32 value, s64 timeout) {
@@ -130,10 +158,10 @@ ResultCode AddressArbiter::WaitForAddressIfEqual(VAddr address, s32 value, s64 t
return RESULT_TIMEOUT;
}
- return WaitForAddress(address, timeout);
+ return WaitForAddressImpl(address, timeout);
}
-ResultCode AddressArbiter::WaitForAddress(VAddr address, s64 timeout) {
+ResultCode AddressArbiter::WaitForAddressImpl(VAddr address, s64 timeout) {
SharedPtr<Thread> current_thread = system.CurrentScheduler().GetCurrentThread();
current_thread->SetArbiterWaitAddress(address);
current_thread->SetStatus(ThreadStatus::WaitArb);
diff --git a/src/core/hle/kernel/address_arbiter.h b/src/core/hle/kernel/address_arbiter.h
index e0c36f2e3..ed0d0e69f 100644
--- a/src/core/hle/kernel/address_arbiter.h
+++ b/src/core/hle/kernel/address_arbiter.h
@@ -4,8 +4,10 @@
#pragma once
+#include <vector>
+
#include "common/common_types.h"
-#include "core/hle/kernel/address_arbiter.h"
+#include "core/hle/kernel/object.h"
union ResultCode;
@@ -40,8 +42,15 @@ public:
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);
+
+private:
/// Signals an address being waited on.
- ResultCode SignalToAddress(VAddr address, s32 num_to_wake);
+ 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);
@@ -59,9 +68,8 @@ public:
/// Waits on an address if the value passed is equal to the argument value.
ResultCode WaitForAddressIfEqual(VAddr address, s32 value, s64 timeout);
-private:
// Waits on the given address with a timeout in nanoseconds
- ResultCode WaitForAddress(VAddr address, s64 timeout);
+ ResultCode WaitForAddressImpl(VAddr address, s64 timeout);
// Gets the threads waiting on an address.
std::vector<SharedPtr<Thread>> GetThreadsWaitingOnAddress(VAddr address) const;
diff --git a/src/core/hle/kernel/client_port.cpp b/src/core/hle/kernel/client_port.cpp
index d4c91d529..744b1697d 100644
--- a/src/core/hle/kernel/client_port.cpp
+++ b/src/core/hle/kernel/client_port.cpp
@@ -2,8 +2,6 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include <tuple>
-
#include "core/hle/kernel/client_port.h"
#include "core/hle/kernel/client_session.h"
#include "core/hle/kernel/errors.h"
@@ -31,17 +29,18 @@ ResultVal<SharedPtr<ClientSession>> ClientPort::Connect() {
active_sessions++;
// Create a new session pair, let the created sessions inherit the parent port's HLE handler.
- auto sessions = ServerSession::CreateSessionPair(kernel, server_port->GetName(), this);
+ auto [server, client] = ServerSession::CreateSessionPair(kernel, server_port->GetName(), this);
- if (server_port->hle_handler)
- server_port->hle_handler->ClientConnected(std::get<SharedPtr<ServerSession>>(sessions));
- else
- server_port->pending_sessions.push_back(std::get<SharedPtr<ServerSession>>(sessions));
+ if (server_port->HasHLEHandler()) {
+ server_port->GetHLEHandler()->ClientConnected(server);
+ } else {
+ server_port->AppendPendingSession(server);
+ }
// Wake the threads waiting on the ServerPort
server_port->WakeupAllWaitingThreads();
- return MakeResult(std::get<SharedPtr<ClientSession>>(sessions));
+ return MakeResult(client);
}
void ClientPort::ConnectionClosed() {
diff --git a/src/core/hle/kernel/client_port.h b/src/core/hle/kernel/client_port.h
index 6cd607206..4921ad4f0 100644
--- a/src/core/hle/kernel/client_port.h
+++ b/src/core/hle/kernel/client_port.h
@@ -25,7 +25,7 @@ public:
return name;
}
- static const HandleType HANDLE_TYPE = HandleType::ClientPort;
+ static constexpr HandleType HANDLE_TYPE = HandleType::ClientPort;
HandleType GetHandleType() const override {
return HANDLE_TYPE;
}
diff --git a/src/core/hle/kernel/client_session.cpp b/src/core/hle/kernel/client_session.cpp
index 704e82824..c17baa50a 100644
--- a/src/core/hle/kernel/client_session.cpp
+++ b/src/core/hle/kernel/client_session.cpp
@@ -17,21 +17,11 @@ ClientSession::~ClientSession() {
// This destructor will be called automatically when the last ClientSession handle is closed by
// the emulated application.
- // Local references to ServerSession and SessionRequestHandler are necessary to guarantee they
+ // A local reference to the ServerSession is necessary to guarantee it
// will be kept alive until after ClientDisconnected() returns.
SharedPtr<ServerSession> server = parent->server;
if (server) {
- std::shared_ptr<SessionRequestHandler> hle_handler = server->hle_handler;
- if (hle_handler)
- hle_handler->ClientDisconnected(server);
-
- // TODO(Subv): Force a wake up of all the ServerSession's waiting threads and set
- // their WaitSynchronization result to 0xC920181A.
-
- // Clean up the list of client threads with pending requests, they are unneeded now that the
- // client endpoint is closed.
- server->pending_requesting_threads.clear();
- server->currently_handling = nullptr;
+ server->ClientDisconnected();
}
parent->client = nullptr;
diff --git a/src/core/hle/kernel/client_session.h b/src/core/hle/kernel/client_session.h
index 4c18de69c..09cdff588 100644
--- a/src/core/hle/kernel/client_session.h
+++ b/src/core/hle/kernel/client_session.h
@@ -29,21 +29,22 @@ public:
return name;
}
- static const HandleType HANDLE_TYPE = HandleType::ClientSession;
+ static constexpr HandleType HANDLE_TYPE = HandleType::ClientSession;
HandleType GetHandleType() const override {
return HANDLE_TYPE;
}
ResultCode SendSyncRequest(SharedPtr<Thread> thread);
- std::string name; ///< Name of client port (optional)
+private:
+ explicit ClientSession(KernelCore& kernel);
+ ~ClientSession() override;
/// The parent session, which links to the server endpoint.
std::shared_ptr<Session> parent;
-private:
- explicit ClientSession(KernelCore& kernel);
- ~ClientSession() override;
+ /// Name of the client session (optional)
+ std::string name;
};
} // namespace Kernel
diff --git a/src/core/hle/kernel/code_set.cpp b/src/core/hle/kernel/code_set.cpp
new file mode 100644
index 000000000..1f434e9af
--- /dev/null
+++ b/src/core/hle/kernel/code_set.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/kernel/code_set.h"
+
+namespace Kernel {
+
+CodeSet::CodeSet() = default;
+CodeSet::~CodeSet() = default;
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/code_set.h b/src/core/hle/kernel/code_set.h
new file mode 100644
index 000000000..879957dcb
--- /dev/null
+++ b/src/core/hle/kernel/code_set.h
@@ -0,0 +1,89 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <cstddef>
+#include <vector>
+
+#include "common/common_types.h"
+
+namespace Kernel {
+
+/**
+ * Represents executable data that may be loaded into a kernel process.
+ *
+ * A code set consists of three basic segments:
+ * - A code (AKA text) segment,
+ * - A read-only data segment (rodata)
+ * - A data segment
+ *
+ * The code segment is the portion of the object file that contains
+ * executable instructions.
+ *
+ * The read-only data segment in the portion of the object file that
+ * contains (as one would expect) read-only data, such as fixed constant
+ * values and data structures.
+ *
+ * The data segment is similar to the read-only data segment -- it contains
+ * variables and data structures that have predefined values, however,
+ * entities within this segment can be modified.
+ */
+struct CodeSet final {
+ /// A single segment within a code set.
+ struct Segment final {
+ /// The byte offset that this segment is located at.
+ std::size_t offset = 0;
+
+ /// The address to map this segment to.
+ VAddr addr = 0;
+
+ /// The size of this segment in bytes.
+ u32 size = 0;
+ };
+
+ explicit CodeSet();
+ ~CodeSet();
+
+ CodeSet(const CodeSet&) = delete;
+ CodeSet& operator=(const CodeSet&) = delete;
+
+ CodeSet(CodeSet&&) = default;
+ CodeSet& operator=(CodeSet&&) = default;
+
+ Segment& CodeSegment() {
+ return segments[0];
+ }
+
+ const Segment& CodeSegment() const {
+ return segments[0];
+ }
+
+ Segment& RODataSegment() {
+ return segments[1];
+ }
+
+ const Segment& RODataSegment() const {
+ return segments[1];
+ }
+
+ Segment& DataSegment() {
+ return segments[2];
+ }
+
+ const Segment& DataSegment() const {
+ return segments[2];
+ }
+
+ /// The overall data that backs this code set.
+ std::vector<u8> memory;
+
+ /// The segments that comprise this code set.
+ std::array<Segment, 3> segments;
+
+ /// The entry point address for this code set.
+ VAddr entrypoint = 0;
+};
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp
index 5dd855db8..f3da525d6 100644
--- a/src/core/hle/kernel/hle_ipc.cpp
+++ b/src/core/hle/kernel/hle_ipc.cpp
@@ -43,7 +43,7 @@ void SessionRequestHandler::ClientDisconnected(const SharedPtr<ServerSession>& s
}
SharedPtr<WritableEvent> HLERequestContext::SleepClientThread(
- SharedPtr<Thread> thread, const std::string& reason, u64 timeout, WakeupCallback&& callback,
+ const std::string& reason, u64 timeout, WakeupCallback&& callback,
SharedPtr<WritableEvent> writable_event) {
// Put the client thread to sleep until the wait event is signaled or the timeout expires.
thread->SetWakeupCallback([context = *this, callback](
@@ -58,7 +58,7 @@ SharedPtr<WritableEvent> HLERequestContext::SleepClientThread(
auto& kernel = Core::System::GetInstance().Kernel();
if (!writable_event) {
// Create event if not provided
- const auto pair = WritableEvent::CreateEventPair(kernel, Kernel::ResetType::OneShot,
+ const auto pair = WritableEvent::CreateEventPair(kernel, ResetType::Automatic,
"HLE Pause Event: " + reason);
writable_event = pair.writable;
}
@@ -76,8 +76,9 @@ SharedPtr<WritableEvent> HLERequestContext::SleepClientThread(
return writable_event;
}
-HLERequestContext::HLERequestContext(SharedPtr<Kernel::ServerSession> server_session)
- : server_session(std::move(server_session)) {
+HLERequestContext::HLERequestContext(SharedPtr<Kernel::ServerSession> server_session,
+ SharedPtr<Thread> thread)
+ : server_session(std::move(server_session)), thread(std::move(thread)) {
cmd_buf[0] = 0;
}
@@ -86,7 +87,7 @@ HLERequestContext::~HLERequestContext() = default;
void HLERequestContext::ParseCommandBuffer(const HandleTable& handle_table, u32_le* src_cmdbuf,
bool incoming) {
IPC::RequestParser rp(src_cmdbuf);
- command_header = std::make_shared<IPC::CommandHeader>(rp.PopRaw<IPC::CommandHeader>());
+ command_header = rp.PopRaw<IPC::CommandHeader>();
if (command_header->type == IPC::CommandType::Close) {
// Close does not populate the rest of the IPC header
@@ -95,8 +96,7 @@ void HLERequestContext::ParseCommandBuffer(const HandleTable& handle_table, u32_
// If handle descriptor is present, add size of it
if (command_header->enable_handle_descriptor) {
- handle_descriptor_header =
- std::make_shared<IPC::HandleDescriptorHeader>(rp.PopRaw<IPC::HandleDescriptorHeader>());
+ handle_descriptor_header = rp.PopRaw<IPC::HandleDescriptorHeader>();
if (handle_descriptor_header->send_current_pid) {
rp.Skip(2, false);
}
@@ -140,16 +140,15 @@ void HLERequestContext::ParseCommandBuffer(const HandleTable& handle_table, u32_
// If this is an incoming message, only CommandType "Request" has a domain header
// All outgoing domain messages have the domain header, if only incoming has it
if (incoming || domain_message_header) {
- domain_message_header =
- std::make_shared<IPC::DomainMessageHeader>(rp.PopRaw<IPC::DomainMessageHeader>());
+ domain_message_header = rp.PopRaw<IPC::DomainMessageHeader>();
} else {
- if (Session()->IsDomain())
+ if (Session()->IsDomain()) {
LOG_WARNING(IPC, "Domain request has no DomainMessageHeader!");
+ }
}
}
- data_payload_header =
- std::make_shared<IPC::DataPayloadHeader>(rp.PopRaw<IPC::DataPayloadHeader>());
+ data_payload_header = rp.PopRaw<IPC::DataPayloadHeader>();
data_payload_offset = rp.GetCurrentOffset();
@@ -264,11 +263,11 @@ ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(Thread& thread) {
// Write the domain objects to the command buffer, these go after the raw untranslated data.
// TODO(Subv): This completely ignores C buffers.
std::size_t domain_offset = size - domain_message_header->num_objects;
- auto& request_handlers = server_session->domain_request_handlers;
- for (auto& object : domain_objects) {
- request_handlers.emplace_back(object);
- dst_cmdbuf[domain_offset++] = static_cast<u32_le>(request_handlers.size());
+ for (const auto& object : domain_objects) {
+ server_session->AppendDomainRequestHandler(object);
+ dst_cmdbuf[domain_offset++] =
+ static_cast<u32_le>(server_session->NumDomainRequestHandlers());
}
}
diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h
index 0107acea4..ccf5e56aa 100644
--- a/src/core/hle/kernel/hle_ipc.h
+++ b/src/core/hle/kernel/hle_ipc.h
@@ -6,6 +6,7 @@
#include <array>
#include <memory>
+#include <optional>
#include <string>
#include <type_traits>
#include <vector>
@@ -96,7 +97,7 @@ protected:
*/
class HLERequestContext {
public:
- explicit HLERequestContext(SharedPtr<ServerSession> session);
+ explicit HLERequestContext(SharedPtr<ServerSession> session, SharedPtr<Thread> thread);
~HLERequestContext();
/// Returns a pointer to the IPC command buffer for this request.
@@ -118,7 +119,6 @@ public:
/**
* Puts the specified guest thread to sleep until the returned event is signaled or until the
* specified timeout expires.
- * @param thread Thread to be put to sleep.
* @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.
@@ -129,8 +129,8 @@ public:
* created.
* @returns Event that when signaled will resume the thread and call the callback function.
*/
- SharedPtr<WritableEvent> SleepClientThread(SharedPtr<Thread> thread, const std::string& reason,
- u64 timeout, WakeupCallback&& callback,
+ SharedPtr<WritableEvent> SleepClientThread(const std::string& reason, u64 timeout,
+ WakeupCallback&& callback,
SharedPtr<WritableEvent> writable_event = nullptr);
/// Populates this context with data from the requesting process/thread.
@@ -168,12 +168,12 @@ public:
return buffer_c_desciptors;
}
- const IPC::DomainMessageHeader* GetDomainMessageHeader() const {
- return domain_message_header.get();
+ const IPC::DomainMessageHeader& GetDomainMessageHeader() const {
+ return domain_message_header.value();
}
bool HasDomainMessageHeader() const {
- return domain_message_header != nullptr;
+ return domain_message_header.has_value();
}
/// Helper function to read a buffer using the appropriate buffer descriptor
@@ -267,15 +267,16 @@ private:
std::array<u32, IPC::COMMAND_BUFFER_LENGTH> cmd_buf;
SharedPtr<Kernel::ServerSession> server_session;
+ SharedPtr<Thread> thread;
// TODO(yuriks): Check common usage of this and optimize size accordingly
boost::container::small_vector<SharedPtr<Object>, 8> move_objects;
boost::container::small_vector<SharedPtr<Object>, 8> copy_objects;
boost::container::small_vector<std::shared_ptr<SessionRequestHandler>, 8> domain_objects;
- std::shared_ptr<IPC::CommandHeader> command_header;
- std::shared_ptr<IPC::HandleDescriptorHeader> handle_descriptor_header;
- std::shared_ptr<IPC::DataPayloadHeader> data_payload_header;
- std::shared_ptr<IPC::DomainMessageHeader> domain_message_header;
+ std::optional<IPC::CommandHeader> command_header;
+ std::optional<IPC::HandleDescriptorHeader> handle_descriptor_header;
+ std::optional<IPC::DataPayloadHeader> data_payload_header;
+ std::optional<IPC::DomainMessageHeader> domain_message_header;
std::vector<IPC::BufferDescriptorX> buffer_x_desciptors;
std::vector<IPC::BufferDescriptorABW> buffer_a_desciptors;
std::vector<IPC::BufferDescriptorABW> buffer_b_desciptors;
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp
index 04ea9349e..757e5f21f 100644
--- a/src/core/hle/kernel/kernel.cpp
+++ b/src/core/hle/kernel/kernel.cpp
@@ -21,6 +21,7 @@
#include "core/hle/kernel/thread.h"
#include "core/hle/lock.h"
#include "core/hle/result.h"
+#include "core/memory.h"
namespace Kernel {
@@ -29,12 +30,12 @@ namespace Kernel {
* @param thread_handle The handle of the thread that's been awoken
* @param cycles_late The number of CPU cycles that have passed since the desired wakeup time
*/
-static void ThreadWakeupCallback(u64 thread_handle, [[maybe_unused]] int cycles_late) {
+static void ThreadWakeupCallback(u64 thread_handle, [[maybe_unused]] s64 cycles_late) {
const auto proper_handle = static_cast<Handle>(thread_handle);
const auto& system = Core::System::GetInstance();
// Lock the global kernel mutex when we enter the kernel HLE.
- std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
+ std::lock_guard lock{HLE::g_hle_lock};
SharedPtr<Thread> thread =
system.Kernel().RetrieveThreadFromWakeupCallbackHandleTable(proper_handle);
@@ -45,8 +46,7 @@ static void ThreadWakeupCallback(u64 thread_handle, [[maybe_unused]] int cycles_
bool resume = true;
- if (thread->GetStatus() == ThreadStatus::WaitSynchAny ||
- thread->GetStatus() == ThreadStatus::WaitSynchAll ||
+ if (thread->GetStatus() == ThreadStatus::WaitSynch ||
thread->GetStatus() == ThreadStatus::WaitHLEEvent) {
// Remove the thread from each of its waiting objects' waitlists
for (const auto& object : thread->GetWaitObjects()) {
@@ -62,7 +62,8 @@ static void ThreadWakeupCallback(u64 thread_handle, [[maybe_unused]] int cycles_
if (thread->GetMutexWaitAddress() != 0 || thread->GetCondVarWaitAddress() != 0 ||
thread->GetWaitHandle() != 0) {
- ASSERT(thread->GetStatus() == ThreadStatus::WaitMutex);
+ ASSERT(thread->GetStatus() == ThreadStatus::WaitMutex ||
+ thread->GetStatus() == ThreadStatus::WaitCondVar);
thread->SetMutexWaitAddress(0);
thread->SetCondVarWaitAddress(0);
thread->SetWaitHandle(0);
@@ -87,7 +88,7 @@ static void ThreadWakeupCallback(u64 thread_handle, [[maybe_unused]] int cycles_
}
struct KernelCore::Impl {
- explicit Impl(Core::System& system) : address_arbiter{system}, system{system} {}
+ explicit Impl(Core::System& system) : system{system} {}
void Initialize(KernelCore& kernel) {
Shutdown();
@@ -114,7 +115,7 @@ struct KernelCore::Impl {
// Creates the default system resource limit
void InitializeSystemResourceLimit(KernelCore& kernel) {
- system_resource_limit = ResourceLimit::Create(kernel, "System");
+ system_resource_limit = ResourceLimit::Create(kernel);
// If setting the default system values fails, then something seriously wrong has occurred.
ASSERT(system_resource_limit->SetLimitValue(ResourceType::PhysicalMemory, 0x200000000)
@@ -138,8 +139,6 @@ struct KernelCore::Impl {
std::vector<SharedPtr<Process>> process_list;
Process* current_process = nullptr;
- Kernel::AddressArbiter address_arbiter;
-
SharedPtr<ResourceLimit> system_resource_limit;
Core::Timing::EventType* thread_wakeup_event_type = nullptr;
@@ -182,6 +181,12 @@ void KernelCore::AppendNewProcess(SharedPtr<Process> process) {
void KernelCore::MakeCurrentProcess(Process* process) {
impl->current_process = process;
+
+ if (process == nullptr) {
+ return;
+ }
+
+ Memory::SetCurrentPageTable(*process);
}
Process* KernelCore::CurrentProcess() {
@@ -192,12 +197,8 @@ const Process* KernelCore::CurrentProcess() const {
return impl->current_process;
}
-AddressArbiter& KernelCore::AddressArbiter() {
- return impl->address_arbiter;
-}
-
-const AddressArbiter& KernelCore::AddressArbiter() const {
- return impl->address_arbiter;
+const std::vector<SharedPtr<Process>>& KernelCore::GetProcessList() const {
+ return impl->process_list;
}
void KernelCore::AddNamedPort(std::string name, SharedPtr<ClientPort> port) {
diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h
index 4d292aca9..6b8738599 100644
--- a/src/core/hle/kernel/kernel.h
+++ b/src/core/hle/kernel/kernel.h
@@ -8,9 +8,6 @@
#include <unordered_map>
#include "core/hle/kernel/object.h"
-template <typename T>
-class ResultVal;
-
namespace Core {
class System;
}
@@ -75,11 +72,8 @@ public:
/// Retrieves a const pointer to the current process.
const Process* CurrentProcess() const;
- /// Provides a reference to the kernel's address arbiter.
- Kernel::AddressArbiter& AddressArbiter();
-
- /// Provides a const reference to the kernel's address arbiter.
- const Kernel::AddressArbiter& AddressArbiter() const;
+ /// Retrieves the list of processes.
+ const std::vector<SharedPtr<Process>>& GetProcessList() const;
/// Adds a port to the named port table
void AddNamedPort(std::string name, SharedPtr<ClientPort> port);
diff --git a/src/core/hle/kernel/mutex.cpp b/src/core/hle/kernel/mutex.cpp
index 0743670ad..98e87313b 100644
--- a/src/core/hle/kernel/mutex.cpp
+++ b/src/core/hle/kernel/mutex.cpp
@@ -2,7 +2,6 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include <map>
#include <utility>
#include <vector>
@@ -10,8 +9,11 @@
#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"
@@ -57,41 +59,47 @@ static void TransferMutexOwnership(VAddr mutex_addr, SharedPtr<Thread> current_t
}
}
-ResultCode Mutex::TryAcquire(HandleTable& handle_table, VAddr address, Handle holding_thread_handle,
+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) {
return ERR_INVALID_ADDRESS;
}
+ const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
+ Thread* const current_thread = system.CurrentScheduler().GetCurrentThread();
SharedPtr<Thread> holding_thread = handle_table.Get<Thread>(holding_thread_handle);
SharedPtr<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 == GetCurrentThread());
+ ASSERT(requesting_thread == current_thread);
- u32 addr_value = Memory::Read32(address);
+ const u32 addr_value = 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)
+ if (holding_thread == nullptr) {
return ERR_INVALID_HANDLE;
+ }
// Wait until the mutex is released
- GetCurrentThread()->SetMutexWaitAddress(address);
- GetCurrentThread()->SetWaitHandle(requesting_thread_handle);
+ current_thread->SetMutexWaitAddress(address);
+ current_thread->SetWaitHandle(requesting_thread_handle);
- GetCurrentThread()->SetStatus(ThreadStatus::WaitMutex);
- GetCurrentThread()->InvalidateWakeupCallback();
+ current_thread->SetStatus(ThreadStatus::WaitMutex);
+ current_thread->InvalidateWakeupCallback();
// Update the lock holder thread's priority to prevent priority inversion.
- holding_thread->AddMutexWaiter(GetCurrentThread());
+ holding_thread->AddMutexWaiter(current_thread);
- Core::System::GetInstance().PrepareReschedule();
+ system.PrepareReschedule();
return RESULT_SUCCESS;
}
@@ -102,7 +110,8 @@ ResultCode Mutex::Release(VAddr address) {
return ERR_INVALID_ADDRESS;
}
- auto [thread, num_waiters] = GetHighestPriorityMutexWaitingThread(GetCurrentThread(), address);
+ auto* const current_thread = system.CurrentScheduler().GetCurrentThread();
+ auto [thread, num_waiters] = GetHighestPriorityMutexWaitingThread(current_thread, address);
// There are no more threads waiting for the mutex, release it completely.
if (thread == nullptr) {
@@ -111,7 +120,7 @@ ResultCode Mutex::Release(VAddr address) {
}
// Transfer the ownership of the mutex from the previous owner to the new one.
- TransferMutexOwnership(address, GetCurrentThread(), thread);
+ TransferMutexOwnership(address, current_thread, thread);
u32 mutex_value = thread->GetWaitHandle();
diff --git a/src/core/hle/kernel/mutex.h b/src/core/hle/kernel/mutex.h
index 81e62d497..b904de2e8 100644
--- a/src/core/hle/kernel/mutex.h
+++ b/src/core/hle/kernel/mutex.h
@@ -5,32 +5,34 @@
#pragma once
#include "common/common_types.h"
-#include "core/hle/kernel/object.h"
union ResultCode;
-namespace Kernel {
+namespace Core {
+class System;
+}
-class HandleTable;
-class Thread;
+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.
- static ResultCode TryAcquire(HandleTable& handle_table, VAddr address,
- Handle holding_thread_handle, Handle requesting_thread_handle);
+ ResultCode TryAcquire(VAddr address, Handle holding_thread_handle,
+ Handle requesting_thread_handle);
/// Releases the mutex at the specified address.
- static ResultCode Release(VAddr address);
+ ResultCode Release(VAddr address);
private:
- Mutex() = default;
- ~Mutex() = default;
+ Core::System& system;
};
} // namespace Kernel
diff --git a/src/core/hle/kernel/object.cpp b/src/core/hle/kernel/object.cpp
index 8870463d0..10431e94c 100644
--- a/src/core/hle/kernel/object.cpp
+++ b/src/core/hle/kernel/object.cpp
@@ -23,7 +23,7 @@ bool Object::IsWaitable() const {
case HandleType::Unknown:
case HandleType::WritableEvent:
case HandleType::SharedMemory:
- case HandleType::AddressArbiter:
+ case HandleType::TransferMemory:
case HandleType::ResourceLimit:
case HandleType::ClientPort:
case HandleType::ClientSession:
diff --git a/src/core/hle/kernel/object.h b/src/core/hle/kernel/object.h
index 4c2505908..2821176a7 100644
--- a/src/core/hle/kernel/object.h
+++ b/src/core/hle/kernel/object.h
@@ -22,9 +22,9 @@ enum class HandleType : u32 {
WritableEvent,
ReadableEvent,
SharedMemory,
+ TransferMemory,
Thread,
Process,
- AddressArbiter,
ResourceLimit,
ClientPort,
ServerPort,
@@ -33,8 +33,8 @@ enum class HandleType : u32 {
};
enum class ResetType {
- OneShot, ///< Reset automatically on object acquisition
- Sticky, ///< Never reset automatically
+ Automatic, ///< Reset automatically on object acquisition
+ Manual, ///< Never reset automatically
};
class Object : NonCopyable {
diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp
index 8009150e0..2b81a8d4f 100644
--- a/src/core/hle/kernel/process.cpp
+++ b/src/core/hle/kernel/process.cpp
@@ -5,10 +5,12 @@
#include <algorithm>
#include <memory>
#include <random>
+#include "common/alignment.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "core/core.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/kernel.h"
#include "core/hle/kernel/process.h"
@@ -26,36 +28,30 @@ namespace {
*
* @param owner_process The parent process for the main thread
* @param kernel The kernel instance to create the main thread under.
- * @param entry_point The address at which the thread should start execution
* @param priority The priority to give the main thread
*/
-void SetupMainThread(Process& owner_process, KernelCore& kernel, VAddr entry_point, u32 priority) {
- // Setup page table so we can write to memory
- SetCurrentPageTable(&owner_process.VMManager().page_table);
-
- // Initialize new "main" thread
- const VAddr stack_top = owner_process.VMManager().GetTLSIORegionEndAddress();
+void SetupMainThread(Process& owner_process, KernelCore& kernel, u32 priority) {
+ const auto& vm_manager = owner_process.VMManager();
+ const VAddr entry_point = vm_manager.GetCodeRegionBaseAddress();
+ const VAddr stack_top = vm_manager.GetTLSIORegionEndAddress();
auto thread_res = Thread::Create(kernel, "main", entry_point, priority, 0,
owner_process.GetIdealCore(), stack_top, owner_process);
SharedPtr<Thread> thread = std::move(thread_res).Unwrap();
// Register 1 must be a handle to the main thread
- const Handle guest_handle = owner_process.GetHandleTable().Create(thread).Unwrap();
- thread->SetGuestHandle(guest_handle);
- thread->GetContext().cpu_registers[1] = guest_handle;
+ const Handle thread_handle = owner_process.GetHandleTable().Create(thread).Unwrap();
+ thread->GetContext().cpu_registers[1] = thread_handle;
// Threads by default are dormant, wake up the main thread so it runs when the scheduler fires
thread->ResumeFromWait();
}
} // Anonymous namespace
-CodeSet::CodeSet() = default;
-CodeSet::~CodeSet() = default;
-
-SharedPtr<Process> Process::Create(KernelCore& kernel, std::string&& name) {
- SharedPtr<Process> process(new Process(kernel));
+SharedPtr<Process> Process::Create(Core::System& system, std::string name) {
+ auto& kernel = system.Kernel();
+ SharedPtr<Process> process(new Process(system));
process->name = std::move(name);
process->resource_limit = kernel.GetSystemResourceLimit();
process->status = ProcessStatus::Created;
@@ -76,6 +72,34 @@ SharedPtr<ResourceLimit> Process::GetResourceLimit() const {
return resource_limit;
}
+u64 Process::GetTotalPhysicalMemoryAvailable() const {
+ return vm_manager.GetTotalPhysicalMemoryAvailable();
+}
+
+u64 Process::GetTotalPhysicalMemoryAvailableWithoutMmHeap() const {
+ // TODO: Subtract the personal heap size from this when the
+ // personal heap is implemented.
+ return GetTotalPhysicalMemoryAvailable();
+}
+
+u64 Process::GetTotalPhysicalMemoryUsed() const {
+ return vm_manager.GetCurrentHeapSize() + main_thread_stack_size + code_memory_size;
+}
+
+u64 Process::GetTotalPhysicalMemoryUsedWithoutMmHeap() const {
+ // TODO: Subtract the personal heap size from this when the
+ // personal heap is implemented.
+ return GetTotalPhysicalMemoryUsed();
+}
+
+void Process::RegisterThread(const Thread* thread) {
+ thread_list.push_back(thread);
+}
+
+void Process::UnregisterThread(const Thread* thread) {
+ thread_list.remove(thread);
+}
+
ResultCode Process::ClearSignalState() {
if (status == ProcessStatus::Exited) {
LOG_ERROR(Kernel, "called on a terminated process instance.");
@@ -108,20 +132,23 @@ ResultCode Process::LoadFromMetadata(const FileSys::ProgramMetadata& metadata) {
return handle_table.SetSize(capabilities.GetHandleTableSize());
}
-void Process::Run(VAddr entry_point, s32 main_thread_priority, u32 stack_size) {
+void Process::Run(s32 main_thread_priority, u64 stack_size) {
+ // The kernel always ensures that the given stack size is page aligned.
+ main_thread_stack_size = Common::AlignUp(stack_size, Memory::PAGE_SIZE);
+
// Allocate and map the main thread stack
// TODO(bunnei): This is heap area that should be allocated by the kernel and not mapped as part
// of the user address space.
+ const VAddr mapping_address = vm_manager.GetTLSIORegionEndAddress() - main_thread_stack_size;
vm_manager
- .MapMemoryBlock(vm_manager.GetTLSIORegionEndAddress() - stack_size,
- std::make_shared<std::vector<u8>>(stack_size, 0), 0, stack_size,
- MemoryState::Stack)
+ .MapMemoryBlock(mapping_address, std::make_shared<std::vector<u8>>(main_thread_stack_size),
+ 0, main_thread_stack_size, MemoryState::Stack)
.Unwrap();
vm_manager.LogLayout();
ChangeStatus(ProcessStatus::Running);
- SetupMainThread(*this, kernel, entry_point, main_thread_priority);
+ SetupMainThread(*this, kernel, main_thread_priority);
}
void Process::PrepareForTermination() {
@@ -132,19 +159,17 @@ void Process::PrepareForTermination() {
if (thread->GetOwnerProcess() != this)
continue;
- if (thread == GetCurrentThread())
+ if (thread == system.CurrentScheduler().GetCurrentThread())
continue;
// TODO(Subv): When are the other running/ready threads terminated?
- ASSERT_MSG(thread->GetStatus() == ThreadStatus::WaitSynchAny ||
- thread->GetStatus() == ThreadStatus::WaitSynchAll,
+ ASSERT_MSG(thread->GetStatus() == ThreadStatus::WaitSynch,
"Exiting processes with non-waiting threads is currently unimplemented");
thread->Stop();
}
};
- const auto& system = Core::System::GetInstance();
stop_threads(system.Scheduler(0).GetThreadList());
stop_threads(system.Scheduler(1).GetThreadList());
stop_threads(system.Scheduler(2).GetThreadList());
@@ -212,35 +237,36 @@ void Process::FreeTLSSlot(VAddr tls_address) {
}
void Process::LoadModule(CodeSet module_, VAddr base_addr) {
- const auto MapSegment = [&](CodeSet::Segment& segment, VMAPermission permissions,
+ const auto memory = std::make_shared<std::vector<u8>>(std::move(module_.memory));
+
+ const auto MapSegment = [&](const CodeSet::Segment& segment, VMAPermission permissions,
MemoryState memory_state) {
const auto vma = vm_manager
- .MapMemoryBlock(segment.addr + base_addr, module_.memory,
- segment.offset, segment.size, memory_state)
+ .MapMemoryBlock(segment.addr + base_addr, memory, segment.offset,
+ segment.size, memory_state)
.Unwrap();
vm_manager.Reprotect(vma, permissions);
};
// Map CodeSet segments
- MapSegment(module_.CodeSegment(), VMAPermission::ReadExecute, MemoryState::CodeStatic);
- MapSegment(module_.RODataSegment(), VMAPermission::Read, MemoryState::CodeMutable);
- MapSegment(module_.DataSegment(), VMAPermission::ReadWrite, MemoryState::CodeMutable);
-
- // Clear instruction cache in CPU JIT
- Core::System::GetInstance().ArmInterface(0).ClearInstructionCache();
- Core::System::GetInstance().ArmInterface(1).ClearInstructionCache();
- Core::System::GetInstance().ArmInterface(2).ClearInstructionCache();
- Core::System::GetInstance().ArmInterface(3).ClearInstructionCache();
+ MapSegment(module_.CodeSegment(), VMAPermission::ReadExecute, MemoryState::Code);
+ MapSegment(module_.RODataSegment(), VMAPermission::Read, MemoryState::CodeData);
+ MapSegment(module_.DataSegment(), VMAPermission::ReadWrite, MemoryState::CodeData);
+
+ code_memory_size += module_.memory.size();
}
-Kernel::Process::Process(KernelCore& kernel) : WaitObject{kernel} {}
-Kernel::Process::~Process() {}
+Process::Process(Core::System& system)
+ : WaitObject{system.Kernel()}, vm_manager{system},
+ address_arbiter{system}, mutex{system}, system{system} {}
+
+Process::~Process() = default;
void Process::Acquire(Thread* thread) {
ASSERT_MSG(!ShouldWait(thread), "Object unavailable!");
}
-bool Process::ShouldWait(Thread* thread) const {
+bool Process::ShouldWait(const Thread* thread) const {
return !is_signaled;
}
diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h
index dcc57ae9f..29e016983 100644
--- a/src/core/hle/kernel/process.h
+++ b/src/core/hle/kernel/process.h
@@ -7,17 +7,22 @@
#include <array>
#include <bitset>
#include <cstddef>
-#include <memory>
+#include <list>
#include <string>
#include <vector>
-#include <boost/container/static_vector.hpp>
#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/process_capability.h"
#include "core/hle/kernel/vm_manager.h"
#include "core/hle/kernel/wait_object.h"
#include "core/hle/result.h"
+namespace Core {
+class System;
+}
+
namespace FileSys {
class ProgramMetadata;
}
@@ -28,13 +33,7 @@ class KernelCore;
class ResourceLimit;
class Thread;
-struct AddressMapping {
- // Address and size must be page-aligned
- VAddr address;
- u64 size;
- bool read_only;
- bool unk_flag;
-};
+struct CodeSet;
enum class MemoryRegion : u16 {
APPLICATION = 1,
@@ -60,46 +59,6 @@ enum class ProcessStatus {
DebugBreak,
};
-struct CodeSet final {
- struct Segment {
- std::size_t offset = 0;
- VAddr addr = 0;
- u32 size = 0;
- };
-
- explicit CodeSet();
- ~CodeSet();
-
- Segment& CodeSegment() {
- return segments[0];
- }
-
- const Segment& CodeSegment() const {
- return segments[0];
- }
-
- Segment& RODataSegment() {
- return segments[1];
- }
-
- const Segment& RODataSegment() const {
- return segments[1];
- }
-
- Segment& DataSegment() {
- return segments[2];
- }
-
- const Segment& DataSegment() const {
- return segments[2];
- }
-
- std::shared_ptr<std::vector<u8>> memory;
-
- std::array<Segment, 3> segments;
- VAddr entrypoint = 0;
-};
-
class Process final : public WaitObject {
public:
enum : u64 {
@@ -116,7 +75,7 @@ public:
static constexpr std::size_t RANDOM_ENTROPY_SIZE = 4;
- static SharedPtr<Process> Create(KernelCore& kernel, std::string&& name);
+ static SharedPtr<Process> Create(Core::System& system, std::string name);
std::string GetTypeName() const override {
return "Process";
@@ -125,7 +84,7 @@ public:
return name;
}
- static const HandleType HANDLE_TYPE = HandleType::Process;
+ static constexpr HandleType HANDLE_TYPE = HandleType::Process;
HandleType GetHandleType() const override {
return HANDLE_TYPE;
}
@@ -150,6 +109,26 @@ public:
return handle_table;
}
+ /// Gets a reference to the process' address arbiter.
+ AddressArbiter& GetAddressArbiter() {
+ return address_arbiter;
+ }
+
+ /// Gets a const reference to the process' address arbiter.
+ const AddressArbiter& GetAddressArbiter() const {
+ return address_arbiter;
+ }
+
+ /// Gets a reference to the process' mutex lock.
+ Mutex& GetMutex() {
+ return mutex;
+ }
+
+ /// Gets a const reference to the process' mutex lock
+ const Mutex& GetMutex() const {
+ return mutex;
+ }
+
/// Gets the current status of the process
ProcessStatus GetStatus() const {
return status;
@@ -207,6 +186,33 @@ public:
return random_entropy.at(index);
}
+ /// Retrieves the total physical memory available to this process in bytes.
+ u64 GetTotalPhysicalMemoryAvailable() const;
+
+ /// Retrieves the total physical memory available to this process in bytes,
+ /// without the size of the personal heap added to it.
+ u64 GetTotalPhysicalMemoryAvailableWithoutMmHeap() const;
+
+ /// Retrieves the total physical memory used by this process in bytes.
+ u64 GetTotalPhysicalMemoryUsed() const;
+
+ /// Retrieves the total physical memory used by this process in bytes,
+ /// without the size of the personal heap added to it.
+ u64 GetTotalPhysicalMemoryUsedWithoutMmHeap() const;
+
+ /// Gets the list of all threads created with this process as their owner.
+ const std::list<const Thread*>& GetThreadList() const {
+ return thread_list;
+ }
+
+ /// Registers a thread as being created under this process,
+ /// adding it to this process' thread list.
+ void RegisterThread(const Thread* thread);
+
+ /// Unregisters a thread from this process, removing it
+ /// from this process' thread list.
+ void UnregisterThread(const Thread* thread);
+
/// Clears the signaled state of the process if and only if it's signaled.
///
/// @pre The process must not be already terminated. If this is called on a
@@ -229,9 +235,12 @@ public:
ResultCode LoadFromMetadata(const FileSys::ProgramMetadata& metadata);
/**
- * Applies address space changes and launches the process main thread.
+ * Starts the main application thread for this process.
+ *
+ * @param main_thread_priority The priority for the main thread.
+ * @param stack_size The stack size for the main thread in bytes.
*/
- void Run(VAddr entry_point, s32 main_thread_priority, u32 stack_size);
+ void Run(s32 main_thread_priority, u64 stack_size);
/**
* Prepares a process for termination by stopping all of its threads
@@ -251,11 +260,11 @@ public:
void FreeTLSSlot(VAddr tls_address);
private:
- explicit Process(KernelCore& kernel);
+ explicit Process(Core::System& system);
~Process() override;
/// Checks if the specified thread should wait until this process is available.
- bool ShouldWait(Thread* thread) const override;
+ bool ShouldWait(const Thread* thread) const override;
/// Acquires/locks this process for the specified thread if it's available.
void Acquire(Thread* thread) override;
@@ -268,6 +277,12 @@ private:
/// Memory manager for this process.
Kernel::VMManager vm_manager;
+ /// Size of the main thread's stack in bytes.
+ u64 main_thread_stack_size = 0;
+
+ /// Size of the loaded code memory in bytes.
+ u64 code_memory_size = 0;
+
/// Current status of the process
ProcessStatus status;
@@ -309,9 +324,24 @@ private:
/// Per-process handle table for storing created object handles in.
HandleTable handle_table;
+ /// Per-process address arbiter.
+ AddressArbiter 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;
+
/// Random values for svcGetInfo RandomEntropy
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;
+
+ /// System context
+ Core::System& system;
+
+ /// Name of this process
std::string name;
};
diff --git a/src/core/hle/kernel/readable_event.cpp b/src/core/hle/kernel/readable_event.cpp
index 0e5083f70..06463cd26 100644
--- a/src/core/hle/kernel/readable_event.cpp
+++ b/src/core/hle/kernel/readable_event.cpp
@@ -14,15 +14,16 @@ namespace Kernel {
ReadableEvent::ReadableEvent(KernelCore& kernel) : WaitObject{kernel} {}
ReadableEvent::~ReadableEvent() = default;
-bool ReadableEvent::ShouldWait(Thread* thread) const {
+bool ReadableEvent::ShouldWait(const Thread* thread) const {
return !signaled;
}
void ReadableEvent::Acquire(Thread* thread) {
ASSERT_MSG(!ShouldWait(thread), "object unavailable!");
- if (reset_type == ResetType::OneShot)
+ if (reset_type == ResetType::Automatic) {
signaled = false;
+ }
}
void ReadableEvent::Signal() {
diff --git a/src/core/hle/kernel/readable_event.h b/src/core/hle/kernel/readable_event.h
index 77a9c362c..84215f572 100644
--- a/src/core/hle/kernel/readable_event.h
+++ b/src/core/hle/kernel/readable_event.h
@@ -31,12 +31,12 @@ public:
return reset_type;
}
- static const HandleType HANDLE_TYPE = HandleType::ReadableEvent;
+ static constexpr HandleType HANDLE_TYPE = HandleType::ReadableEvent;
HandleType GetHandleType() const override {
return HANDLE_TYPE;
}
- bool ShouldWait(Thread* thread) const override;
+ bool ShouldWait(const Thread* thread) const override;
void Acquire(Thread* thread) override;
/// Unconditionally clears the readable event's state.
diff --git a/src/core/hle/kernel/resource_limit.cpp b/src/core/hle/kernel/resource_limit.cpp
index 2f9695005..173f69915 100644
--- a/src/core/hle/kernel/resource_limit.cpp
+++ b/src/core/hle/kernel/resource_limit.cpp
@@ -16,11 +16,8 @@ constexpr std::size_t ResourceTypeToIndex(ResourceType type) {
ResourceLimit::ResourceLimit(KernelCore& kernel) : Object{kernel} {}
ResourceLimit::~ResourceLimit() = default;
-SharedPtr<ResourceLimit> ResourceLimit::Create(KernelCore& kernel, std::string name) {
- SharedPtr<ResourceLimit> resource_limit(new ResourceLimit(kernel));
-
- resource_limit->name = std::move(name);
- return resource_limit;
+SharedPtr<ResourceLimit> ResourceLimit::Create(KernelCore& kernel) {
+ return new ResourceLimit(kernel);
}
s64 ResourceLimit::GetCurrentResourceValue(ResourceType resource) const {
diff --git a/src/core/hle/kernel/resource_limit.h b/src/core/hle/kernel/resource_limit.h
index 59dc11c22..2613a6bb5 100644
--- a/src/core/hle/kernel/resource_limit.h
+++ b/src/core/hle/kernel/resource_limit.h
@@ -31,19 +31,17 @@ constexpr bool IsValidResourceType(ResourceType type) {
class ResourceLimit final : public Object {
public:
- /**
- * Creates a resource limit object.
- */
- static SharedPtr<ResourceLimit> Create(KernelCore& kernel, std::string name = "Unknown");
+ /// Creates a resource limit object.
+ static SharedPtr<ResourceLimit> Create(KernelCore& kernel);
std::string GetTypeName() const override {
return "ResourceLimit";
}
std::string GetName() const override {
- return name;
+ return GetTypeName();
}
- static const HandleType HANDLE_TYPE = HandleType::ResourceLimit;
+ static constexpr HandleType HANDLE_TYPE = HandleType::ResourceLimit;
HandleType GetHandleType() const override {
return HANDLE_TYPE;
}
@@ -95,9 +93,6 @@ private:
ResourceArray limits{};
/// Current resource limit values.
ResourceArray values{};
-
- /// Name of resource limit object.
- std::string name;
};
} // namespace Kernel
diff --git a/src/core/hle/kernel/scheduler.cpp b/src/core/hle/kernel/scheduler.cpp
index 44f30d070..e8447b69a 100644
--- a/src/core/hle/kernel/scheduler.cpp
+++ b/src/core/hle/kernel/scheduler.cpp
@@ -19,7 +19,8 @@ namespace Kernel {
std::mutex Scheduler::scheduler_mutex;
-Scheduler::Scheduler(Core::ARM_Interface& cpu_core) : cpu_core(cpu_core) {}
+Scheduler::Scheduler(Core::System& system, Core::ARM_Interface& cpu_core)
+ : cpu_core{cpu_core}, system{system} {}
Scheduler::~Scheduler() {
for (auto& thread : thread_list) {
@@ -28,8 +29,8 @@ Scheduler::~Scheduler() {
}
bool Scheduler::HaveReadyThreads() const {
- std::lock_guard<std::mutex> lock(scheduler_mutex);
- return ready_queue.get_first() != nullptr;
+ std::lock_guard lock{scheduler_mutex};
+ return !ready_queue.empty();
}
Thread* Scheduler::GetCurrentThread() const {
@@ -45,23 +46,28 @@ Thread* Scheduler::PopNextReadyThread() {
Thread* thread = GetCurrentThread();
if (thread && thread->GetStatus() == ThreadStatus::Running) {
+ if (ready_queue.empty()) {
+ return thread;
+ }
// We have to do better than the current thread.
// This call returns null when that's not possible.
- next = ready_queue.pop_first_better(thread->GetPriority());
- if (!next) {
- // Otherwise just keep going with the current thread
+ next = ready_queue.front();
+ if (next == nullptr || next->GetPriority() >= thread->GetPriority()) {
next = thread;
}
} else {
- next = ready_queue.pop_first();
+ if (ready_queue.empty()) {
+ return nullptr;
+ }
+ next = ready_queue.front();
}
return next;
}
void Scheduler::SwitchContext(Thread* new_thread) {
- Thread* const previous_thread = GetCurrentThread();
- Process* const previous_process = Core::CurrentProcess();
+ Thread* previous_thread = GetCurrentThread();
+ Process* const previous_process = system.Kernel().CurrentProcess();
UpdateLastContextSwitchTime(previous_thread, previous_process);
@@ -74,7 +80,7 @@ void Scheduler::SwitchContext(Thread* new_thread) {
if (previous_thread->GetStatus() == ThreadStatus::Running) {
// This is only the case when a reschedule is triggered without the current thread
// yielding execution (i.e. an event triggered, system core time-sliced, etc)
- ready_queue.push_front(previous_thread->GetPriority(), previous_thread);
+ ready_queue.add(previous_thread, previous_thread->GetPriority(), false);
previous_thread->SetStatus(ThreadStatus::Ready);
}
}
@@ -89,13 +95,12 @@ void Scheduler::SwitchContext(Thread* new_thread) {
current_thread = new_thread;
- ready_queue.remove(new_thread->GetPriority(), new_thread);
+ ready_queue.remove(new_thread, new_thread->GetPriority());
new_thread->SetStatus(ThreadStatus::Running);
auto* const thread_owner_process = current_thread->GetOwnerProcess();
if (previous_process != thread_owner_process) {
- Core::System::GetInstance().Kernel().MakeCurrentProcess(thread_owner_process);
- SetCurrentPageTable(&Core::CurrentProcess()->VMManager().page_table);
+ system.Kernel().MakeCurrentProcess(thread_owner_process);
}
cpu_core.LoadContext(new_thread->GetContext());
@@ -111,7 +116,7 @@ void Scheduler::SwitchContext(Thread* new_thread) {
void Scheduler::UpdateLastContextSwitchTime(Thread* thread, Process* process) {
const u64 prev_switch_ticks = last_context_switch_time;
- const u64 most_recent_switch_ticks = Core::System::GetInstance().CoreTiming().GetTicks();
+ const u64 most_recent_switch_ticks = system.CoreTiming().GetTicks();
const u64 update_ticks = most_recent_switch_ticks - prev_switch_ticks;
if (thread != nullptr) {
@@ -126,7 +131,7 @@ void Scheduler::UpdateLastContextSwitchTime(Thread* thread, Process* process) {
}
void Scheduler::Reschedule() {
- std::lock_guard<std::mutex> lock(scheduler_mutex);
+ std::lock_guard lock{scheduler_mutex};
Thread* cur = GetCurrentThread();
Thread* next = PopNextReadyThread();
@@ -142,51 +147,54 @@ void Scheduler::Reschedule() {
SwitchContext(next);
}
-void Scheduler::AddThread(SharedPtr<Thread> thread, u32 priority) {
- std::lock_guard<std::mutex> lock(scheduler_mutex);
+void Scheduler::AddThread(SharedPtr<Thread> thread) {
+ std::lock_guard lock{scheduler_mutex};
thread_list.push_back(std::move(thread));
- ready_queue.prepare(priority);
}
void Scheduler::RemoveThread(Thread* thread) {
- std::lock_guard<std::mutex> lock(scheduler_mutex);
+ std::lock_guard lock{scheduler_mutex};
thread_list.erase(std::remove(thread_list.begin(), thread_list.end(), thread),
thread_list.end());
}
void Scheduler::ScheduleThread(Thread* thread, u32 priority) {
- std::lock_guard<std::mutex> lock(scheduler_mutex);
+ std::lock_guard lock{scheduler_mutex};
ASSERT(thread->GetStatus() == ThreadStatus::Ready);
- ready_queue.push_back(priority, thread);
+ ready_queue.add(thread, priority);
}
void Scheduler::UnscheduleThread(Thread* thread, u32 priority) {
- std::lock_guard<std::mutex> lock(scheduler_mutex);
+ std::lock_guard lock{scheduler_mutex};
ASSERT(thread->GetStatus() == ThreadStatus::Ready);
- ready_queue.remove(priority, thread);
+ ready_queue.remove(thread, priority);
}
void Scheduler::SetThreadPriority(Thread* thread, u32 priority) {
- std::lock_guard<std::mutex> lock(scheduler_mutex);
+ std::lock_guard lock{scheduler_mutex};
+ if (thread->GetPriority() == priority) {
+ return;
+ }
// If thread was ready, adjust queues
if (thread->GetStatus() == ThreadStatus::Ready)
- ready_queue.move(thread, thread->GetPriority(), priority);
- else
- ready_queue.prepare(priority);
+ ready_queue.adjust(thread, thread->GetPriority(), priority);
}
Thread* Scheduler::GetNextSuggestedThread(u32 core, u32 maximum_priority) const {
- std::lock_guard<std::mutex> lock(scheduler_mutex);
+ std::lock_guard lock{scheduler_mutex};
const u32 mask = 1U << core;
- return ready_queue.get_first_filter([mask, maximum_priority](Thread const* thread) {
- return (thread->GetAffinityMask() & mask) != 0 && thread->GetPriority() < maximum_priority;
- });
+ for (auto* thread : ready_queue) {
+ if ((thread->GetAffinityMask() & mask) != 0 && thread->GetPriority() < maximum_priority) {
+ return thread;
+ }
+ }
+ return nullptr;
}
void Scheduler::YieldWithoutLoadBalancing(Thread* thread) {
@@ -198,8 +206,7 @@ void Scheduler::YieldWithoutLoadBalancing(Thread* thread) {
ASSERT(thread->GetPriority() < THREADPRIO_COUNT);
// Yield this thread -- sleep for zero time and force reschedule to different thread
- WaitCurrentThread_Sleep();
- GetCurrentThread()->WakeAfterDelay(0);
+ GetCurrentThread()->Sleep(0);
}
void Scheduler::YieldWithLoadBalancing(Thread* thread) {
@@ -214,8 +221,7 @@ void Scheduler::YieldWithLoadBalancing(Thread* thread) {
ASSERT(priority < THREADPRIO_COUNT);
// Sleep for zero time to be able to force reschedule to different thread
- WaitCurrentThread_Sleep();
- GetCurrentThread()->WakeAfterDelay(0);
+ GetCurrentThread()->Sleep(0);
Thread* suggested_thread = nullptr;
@@ -223,8 +229,7 @@ void Scheduler::YieldWithLoadBalancing(Thread* thread) {
// Take the first non-nullptr one
for (unsigned cur_core = 0; cur_core < Core::NUM_CPU_CORES; ++cur_core) {
const auto res =
- Core::System::GetInstance().CpuCore(cur_core).Scheduler().GetNextSuggestedThread(
- core, priority);
+ system.CpuCore(cur_core).Scheduler().GetNextSuggestedThread(core, priority);
// If scheduler provides a suggested thread
if (res != nullptr) {
diff --git a/src/core/hle/kernel/scheduler.h b/src/core/hle/kernel/scheduler.h
index 97ced4dfc..b29bf7be8 100644
--- a/src/core/hle/kernel/scheduler.h
+++ b/src/core/hle/kernel/scheduler.h
@@ -7,13 +7,14 @@
#include <mutex>
#include <vector>
#include "common/common_types.h"
-#include "common/thread_queue_list.h"
+#include "common/multi_level_queue.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/thread.h"
namespace Core {
class ARM_Interface;
-}
+class System;
+} // namespace Core
namespace Kernel {
@@ -21,7 +22,7 @@ class Process;
class Scheduler final {
public:
- explicit Scheduler(Core::ARM_Interface& cpu_core);
+ explicit Scheduler(Core::System& system, Core::ARM_Interface& cpu_core);
~Scheduler();
/// Returns whether there are any threads that are ready to run.
@@ -37,7 +38,7 @@ public:
u64 GetLastContextSwitchTicks() const;
/// Adds a new thread to the scheduler
- void AddThread(SharedPtr<Thread> thread, u32 priority);
+ void AddThread(SharedPtr<Thread> thread);
/// Removes a thread from the scheduler
void RemoveThread(Thread* thread);
@@ -155,13 +156,14 @@ private:
std::vector<SharedPtr<Thread>> thread_list;
/// Lists only ready thread ids.
- Common::ThreadQueueList<Thread*, THREADPRIO_LOWEST + 1> ready_queue;
+ Common::MultiLevelQueue<Thread*, THREADPRIO_LOWEST + 1> ready_queue;
SharedPtr<Thread> current_thread = nullptr;
Core::ARM_Interface& cpu_core;
u64 last_context_switch_time = 0;
+ Core::System& system;
static std::mutex scheduler_mutex;
};
diff --git a/src/core/hle/kernel/server_port.cpp b/src/core/hle/kernel/server_port.cpp
index d6ceeb2da..02e7c60e6 100644
--- a/src/core/hle/kernel/server_port.cpp
+++ b/src/core/hle/kernel/server_port.cpp
@@ -26,7 +26,11 @@ ResultVal<SharedPtr<ServerSession>> ServerPort::Accept() {
return MakeResult(std::move(session));
}
-bool ServerPort::ShouldWait(Thread* thread) const {
+void ServerPort::AppendPendingSession(SharedPtr<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();
}
@@ -35,9 +39,8 @@ void ServerPort::Acquire(Thread* thread) {
ASSERT_MSG(!ShouldWait(thread), "object unavailable!");
}
-std::tuple<SharedPtr<ServerPort>, SharedPtr<ClientPort>> ServerPort::CreatePortPair(
- KernelCore& kernel, u32 max_sessions, std::string name) {
-
+ServerPort::PortPair ServerPort::CreatePortPair(KernelCore& kernel, u32 max_sessions,
+ std::string name) {
SharedPtr<ServerPort> server_port(new ServerPort(kernel));
SharedPtr<ClientPort> client_port(new ClientPort(kernel));
@@ -47,7 +50,7 @@ std::tuple<SharedPtr<ServerPort>, SharedPtr<ClientPort>> ServerPort::CreatePortP
client_port->max_sessions = max_sessions;
client_port->active_sessions = 0;
- return std::make_tuple(std::move(server_port), std::move(client_port));
+ return std::make_pair(std::move(server_port), std::move(client_port));
}
} // namespace Kernel
diff --git a/src/core/hle/kernel/server_port.h b/src/core/hle/kernel/server_port.h
index e52f8245f..dc88a1ebd 100644
--- a/src/core/hle/kernel/server_port.h
+++ b/src/core/hle/kernel/server_port.h
@@ -6,7 +6,7 @@
#include <memory>
#include <string>
-#include <tuple>
+#include <utility>
#include <vector>
#include "common/common_types.h"
#include "core/hle/kernel/object.h"
@@ -22,6 +22,9 @@ class SessionRequestHandler;
class ServerPort final : public WaitObject {
public:
+ using HLEHandler = std::shared_ptr<SessionRequestHandler>;
+ using PortPair = std::pair<SharedPtr<ServerPort>, SharedPtr<ClientPort>>;
+
/**
* Creates a pair of ServerPort and an associated ClientPort.
*
@@ -30,8 +33,8 @@ public:
* @param name Optional name of the ports
* @return The created port tuple
*/
- static std::tuple<SharedPtr<ServerPort>, SharedPtr<ClientPort>> CreatePortPair(
- KernelCore& kernel, u32 max_sessions, std::string name = "UnknownPort");
+ static PortPair CreatePortPair(KernelCore& kernel, u32 max_sessions,
+ std::string name = "UnknownPort");
std::string GetTypeName() const override {
return "ServerPort";
@@ -40,7 +43,7 @@ public:
return name;
}
- static const HandleType HANDLE_TYPE = HandleType::ServerPort;
+ static constexpr HandleType HANDLE_TYPE = HandleType::ServerPort;
HandleType GetHandleType() const override {
return HANDLE_TYPE;
}
@@ -51,29 +54,44 @@ public:
*/
ResultVal<SharedPtr<ServerSession>> Accept();
+ /// Whether or not this server port has an HLE handler available.
+ bool HasHLEHandler() const {
+ return hle_handler != nullptr;
+ }
+
+ /// Gets the HLE handler for this port.
+ HLEHandler GetHLEHandler() const {
+ return hle_handler;
+ }
+
/**
* Sets the HLE handler template for the port. ServerSessions crated by connecting to this port
* will inherit a reference to this handler.
*/
- void SetHleHandler(std::shared_ptr<SessionRequestHandler> hle_handler_) {
+ void SetHleHandler(HLEHandler hle_handler_) {
hle_handler = std::move(hle_handler_);
}
- std::string name; ///< Name of port (optional)
+ /// Appends a ServerSession to the collection of ServerSessions
+ /// waiting to be accepted by this port.
+ void AppendPendingSession(SharedPtr<ServerSession> pending_session);
+
+ bool ShouldWait(const Thread* thread) const override;
+ void Acquire(Thread* thread) override;
+
+private:
+ explicit ServerPort(KernelCore& kernel);
+ ~ServerPort() override;
/// ServerSessions waiting to be accepted by the port
std::vector<SharedPtr<ServerSession>> pending_sessions;
/// This session's HLE request handler template (optional)
/// ServerSessions created from this port inherit a reference to this handler.
- std::shared_ptr<SessionRequestHandler> hle_handler;
-
- bool ShouldWait(Thread* thread) const override;
- void Acquire(Thread* thread) override;
+ HLEHandler hle_handler;
-private:
- explicit ServerPort(KernelCore& kernel);
- ~ServerPort() override;
+ /// Name of the port (optional)
+ std::string name;
};
} // namespace Kernel
diff --git a/src/core/hle/kernel/server_session.cpp b/src/core/hle/kernel/server_session.cpp
index 027434f92..30b2bfb5a 100644
--- a/src/core/hle/kernel/server_session.cpp
+++ b/src/core/hle/kernel/server_session.cpp
@@ -28,11 +28,9 @@ ServerSession::~ServerSession() {
// the emulated application.
// Decrease the port's connection count.
- if (parent->port)
+ if (parent->port) {
parent->port->ConnectionClosed();
-
- // TODO(Subv): Wake up all the ClientSession's waiting threads and set
- // the SendSyncRequest result to 0xC920181A.
+ }
parent->server = nullptr;
}
@@ -46,7 +44,7 @@ ResultVal<SharedPtr<ServerSession>> ServerSession::Create(KernelCore& kernel, st
return MakeResult(std::move(server_session));
}
-bool ServerSession::ShouldWait(Thread* thread) const {
+bool ServerSession::ShouldWait(const Thread* thread) const {
// Closed sessions should never wait, an error will be returned from svcReplyAndReceive.
if (parent->client == nullptr)
return false;
@@ -63,42 +61,68 @@ void ServerSession::Acquire(Thread* thread) {
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
+ // hle_handler member itself during the course of the function executing.
+ std::shared_ptr<SessionRequestHandler> handler = hle_handler;
+ if (handler) {
+ // Note that after this returns, this server session's hle_handler is
+ // invalidated (set to null).
+ handler->ClientDisconnected(this);
+ }
+
+ // Clean up the list of client threads with pending requests, they are unneeded now that the
+ // client endpoint is closed.
+ pending_requesting_threads.clear();
+ currently_handling = nullptr;
+}
+
+void ServerSession::AppendDomainRequestHandler(std::shared_ptr<SessionRequestHandler> handler) {
+ domain_request_handlers.push_back(std::move(handler));
+}
+
+std::size_t ServerSession::NumDomainRequestHandlers() const {
+ return domain_request_handlers.size();
+}
+
ResultCode ServerSession::HandleDomainSyncRequest(Kernel::HLERequestContext& context) {
- auto* const domain_message_header = context.GetDomainMessageHeader();
- if (domain_message_header) {
- // Set domain handlers in HLE context, used for domain objects (IPC interfaces) as inputs
- context.SetDomainRequestHandlers(domain_request_handlers);
-
- // If there is a DomainMessageHeader, then this is CommandType "Request"
- const u32 object_id{context.GetDomainMessageHeader()->object_id};
- switch (domain_message_header->command) {
- case IPC::DomainMessageHeader::CommandType::SendMessage:
- if (object_id > domain_request_handlers.size()) {
- LOG_CRITICAL(IPC,
- "object_id {} is too big! This probably means a recent service call "
- "to {} needed to return a new interface!",
- object_id, name);
- UNREACHABLE();
- return RESULT_SUCCESS; // Ignore error if asserts are off
- }
- return domain_request_handlers[object_id - 1]->HandleSyncRequest(context);
-
- case IPC::DomainMessageHeader::CommandType::CloseVirtualHandle: {
- LOG_DEBUG(IPC, "CloseVirtualHandle, object_id=0x{:08X}", object_id);
-
- domain_request_handlers[object_id - 1] = nullptr;
-
- IPC::ResponseBuilder rb{context, 2};
- rb.Push(RESULT_SUCCESS);
- return RESULT_SUCCESS;
- }
+ if (!context.HasDomainMessageHeader()) {
+ return RESULT_SUCCESS;
+ }
+
+ // Set domain handlers in HLE context, used for domain objects (IPC interfaces) as inputs
+ context.SetDomainRequestHandlers(domain_request_handlers);
+
+ // If there is a DomainMessageHeader, then this is CommandType "Request"
+ const auto& domain_message_header = context.GetDomainMessageHeader();
+ const u32 object_id{domain_message_header.object_id};
+ switch (domain_message_header.command) {
+ case IPC::DomainMessageHeader::CommandType::SendMessage:
+ if (object_id > domain_request_handlers.size()) {
+ LOG_CRITICAL(IPC,
+ "object_id {} is too big! This probably means a recent service call "
+ "to {} needed to return a new interface!",
+ object_id, name);
+ UNREACHABLE();
+ return RESULT_SUCCESS; // Ignore error if asserts are off
}
+ return domain_request_handlers[object_id - 1]->HandleSyncRequest(context);
+
+ case IPC::DomainMessageHeader::CommandType::CloseVirtualHandle: {
+ LOG_DEBUG(IPC, "CloseVirtualHandle, object_id=0x{:08X}", object_id);
- LOG_CRITICAL(IPC, "Unknown domain command={}",
- static_cast<int>(domain_message_header->command.Value()));
- ASSERT(false);
+ domain_request_handlers[object_id - 1] = nullptr;
+
+ IPC::ResponseBuilder rb{context, 2};
+ rb.Push(RESULT_SUCCESS);
+ return RESULT_SUCCESS;
+ }
}
+ LOG_CRITICAL(IPC, "Unknown domain command={}",
+ static_cast<int>(domain_message_header.command.Value()));
+ ASSERT(false);
return RESULT_SUCCESS;
}
@@ -106,7 +130,7 @@ ResultCode ServerSession::HandleSyncRequest(SharedPtr<Thread> thread) {
// The ServerSession received a sync request, this means that there's new data available
// from its ClientSession, so wake up any threads that may be waiting on a svcReplyAndReceive or
// similar.
- Kernel::HLERequestContext context(this);
+ Kernel::HLERequestContext context(this, thread);
u32* cmd_buf = (u32*)Memory::GetPointer(thread->GetTLSAddress());
context.PopulateFromIncomingCommandBuffer(kernel.CurrentProcess()->GetHandleTable(), cmd_buf);
@@ -175,6 +199,6 @@ ServerSession::SessionPair ServerSession::CreateSessionPair(KernelCore& kernel,
client_session->parent = parent;
server_session->parent = parent;
- return std::make_tuple(std::move(server_session), std::move(client_session));
+ return std::make_pair(std::move(server_session), std::move(client_session));
}
} // namespace Kernel
diff --git a/src/core/hle/kernel/server_session.h b/src/core/hle/kernel/server_session.h
index e0e9d64c8..738df30f8 100644
--- a/src/core/hle/kernel/server_session.h
+++ b/src/core/hle/kernel/server_session.h
@@ -6,6 +6,7 @@
#include <memory>
#include <string>
+#include <utility>
#include <vector>
#include "core/hle/kernel/object.h"
@@ -41,12 +42,24 @@ public:
return "ServerSession";
}
- static const HandleType HANDLE_TYPE = HandleType::ServerSession;
+ std::string GetName() const override {
+ return name;
+ }
+
+ static constexpr HandleType HANDLE_TYPE = HandleType::ServerSession;
HandleType GetHandleType() const override {
return HANDLE_TYPE;
}
- using SessionPair = std::tuple<SharedPtr<ServerSession>, SharedPtr<ClientSession>>;
+ Session* GetParent() {
+ return parent.get();
+ }
+
+ const Session* GetParent() const {
+ return parent.get();
+ }
+
+ using SessionPair = std::pair<SharedPtr<ServerSession>, SharedPtr<ClientSession>>;
/**
* Creates a pair of ServerSession and an associated ClientSession.
@@ -74,27 +87,20 @@ public:
*/
ResultCode HandleSyncRequest(SharedPtr<Thread> thread);
- bool ShouldWait(Thread* thread) const override;
+ bool ShouldWait(const Thread* thread) const override;
void Acquire(Thread* thread) override;
- std::string name; ///< The name of this session (optional)
- std::shared_ptr<Session> parent; ///< The parent session, which links to the client endpoint.
- std::shared_ptr<SessionRequestHandler>
- hle_handler; ///< This session's HLE request handler (applicable when not a domain)
+ /// Called when a client disconnection occurs.
+ void ClientDisconnected();
- /// This is the list of domain request handlers (after conversion to a domain)
- std::vector<std::shared_ptr<SessionRequestHandler>> domain_request_handlers;
+ /// Adds a new domain request handler to the collection of request handlers within
+ /// this ServerSession instance.
+ void AppendDomainRequestHandler(std::shared_ptr<SessionRequestHandler> handler);
- /// 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<SharedPtr<Thread>> 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.
- SharedPtr<Thread> currently_handling;
+ /// Retrieves the total number of domain request handlers that have been
+ /// appended to this ServerSession instance.
+ std::size_t NumDomainRequestHandlers() const;
/// Returns true if the session has been converted to a domain, otherwise False
bool IsDomain() const {
@@ -129,8 +135,30 @@ private:
/// object handle.
ResultCode HandleDomainSyncRequest(Kernel::HLERequestContext& context);
+ /// The parent session, which links to the client endpoint.
+ std::shared_ptr<Session> parent;
+
+ /// This session's HLE request handler (applicable when not a domain)
+ std::shared_ptr<SessionRequestHandler> hle_handler;
+
+ /// This is the list of domain request handlers (after conversion to a domain)
+ std::vector<std::shared_ptr<SessionRequestHandler>> domain_request_handlers;
+
+ /// 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<SharedPtr<Thread>> 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.
+ SharedPtr<Thread> currently_handling;
+
/// When set to True, converts the session to a domain at the end of the command
bool convert_to_domain{};
+
+ /// The name of this session (optional)
+ std::string name;
};
} // namespace Kernel
diff --git a/src/core/hle/kernel/shared_memory.cpp b/src/core/hle/kernel/shared_memory.cpp
index 22d0c1dd5..f15c5ee36 100644
--- a/src/core/hle/kernel/shared_memory.cpp
+++ b/src/core/hle/kernel/shared_memory.cpp
@@ -6,11 +6,9 @@
#include "common/assert.h"
#include "common/logging/log.h"
-#include "core/core.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/shared_memory.h"
-#include "core/memory.h"
namespace Kernel {
@@ -34,8 +32,8 @@ SharedPtr<SharedMemory> SharedMemory::Create(KernelCore& kernel, Process* owner_
shared_memory->backing_block_offset = 0;
// Refresh the address mappings for the current process.
- if (Core::CurrentProcess() != nullptr) {
- Core::CurrentProcess()->VMManager().RefreshMemoryBlockMappings(
+ if (kernel.CurrentProcess() != nullptr) {
+ kernel.CurrentProcess()->VMManager().RefreshMemoryBlockMappings(
shared_memory->backing_block.get());
}
} else {
@@ -120,7 +118,15 @@ ResultCode SharedMemory::Map(Process& target_process, VAddr address, MemoryPermi
ConvertPermissions(permissions));
}
-ResultCode SharedMemory::Unmap(Process& target_process, VAddr address) {
+ResultCode SharedMemory::Unmap(Process& target_process, VAddr address, u64 unmap_size) {
+ if (unmap_size != size) {
+ LOG_ERROR(Kernel,
+ "Invalid size passed to Unmap. Size must be equal to the size of the "
+ "memory managed. Shared memory size=0x{:016X}, Unmap size=0x{:016X}",
+ size, unmap_size);
+ return ERR_INVALID_SIZE;
+ }
+
// TODO(Subv): Verify what happens if the application tries to unmap an address that is not
// mapped to a SharedMemory.
return target_process.VMManager().UnmapRange(address, size);
diff --git a/src/core/hle/kernel/shared_memory.h b/src/core/hle/kernel/shared_memory.h
index dab2a6bea..c2b6155e1 100644
--- a/src/core/hle/kernel/shared_memory.h
+++ b/src/core/hle/kernel/shared_memory.h
@@ -76,7 +76,7 @@ public:
return name;
}
- static const HandleType HANDLE_TYPE = HandleType::SharedMemory;
+ static constexpr HandleType HANDLE_TYPE = HandleType::SharedMemory;
HandleType GetHandleType() const override {
return HANDLE_TYPE;
}
@@ -104,11 +104,17 @@ public:
/**
* Unmaps a shared memory block from the specified address in system memory
+ *
* @param target_process Process from which to unmap the memory block.
- * @param address Address in system memory where the shared memory block is mapped
+ * @param address Address in system memory where the shared memory block is mapped.
+ * @param unmap_size The amount of bytes to unmap from this shared memory instance.
+ *
* @return Result code of the unmap operation
+ *
+ * @pre The given size to unmap must be the same size as the amount of memory managed by
+ * the SharedMemory instance itself, otherwise ERR_INVALID_SIZE will be returned.
*/
- ResultCode Unmap(Process& target_process, VAddr address);
+ ResultCode Unmap(Process& target_process, VAddr address, u64 unmap_size);
/**
* Gets a pointer to the shared memory block
diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp
index 7f5c0cc86..f9c606bc5 100644
--- a/src/core/hle/kernel/svc.cpp
+++ b/src/core/hle/kernel/svc.cpp
@@ -32,6 +32,7 @@
#include "core/hle/kernel/svc.h"
#include "core/hle/kernel/svc_wrap.h"
#include "core/hle/kernel/thread.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"
@@ -130,16 +131,15 @@ enum class ResourceLimitValueType {
LimitValue,
};
-ResultVal<s64> RetrieveResourceLimitValue(Handle resource_limit, u32 resource_type,
- ResourceLimitValueType value_type) {
+ResultVal<s64> RetrieveResourceLimitValue(Core::System& system, Handle resource_limit,
+ u32 resource_type, ResourceLimitValueType value_type) {
const auto type = static_cast<ResourceType>(resource_type);
if (!IsValidResourceType(type)) {
LOG_ERROR(Kernel_SVC, "Invalid resource limit type: '{}'", resource_type);
return ERR_INVALID_ENUM_VALUE;
}
- const auto& kernel = Core::System::GetInstance().Kernel();
- const auto* const current_process = kernel.CurrentProcess();
+ const auto* const current_process = system.Kernel().CurrentProcess();
ASSERT(current_process != nullptr);
const auto resource_limit_object =
@@ -159,7 +159,7 @@ ResultVal<s64> RetrieveResourceLimitValue(Handle resource_limit, u32 resource_ty
} // Anonymous namespace
/// Set the process heap to a given Size. It can both extend and shrink the heap.
-static ResultCode SetHeapSize(VAddr* heap_addr, u64 heap_size) {
+static ResultCode SetHeapSize(Core::System& system, VAddr* heap_addr, u64 heap_size) {
LOG_TRACE(Kernel_SVC, "called, heap_size=0x{:X}", heap_size);
// Size must be a multiple of 0x200000 (2MB) and be equal to or less than 8GB.
@@ -174,11 +174,8 @@ static ResultCode SetHeapSize(VAddr* heap_addr, u64 heap_size) {
return ERR_INVALID_SIZE;
}
- auto& vm_manager = Core::CurrentProcess()->VMManager();
- const VAddr heap_base = vm_manager.GetHeapRegionBaseAddress();
- const auto alloc_result =
- vm_manager.HeapAllocate(heap_base, heap_size, VMAPermission::ReadWrite);
-
+ auto& vm_manager = system.Kernel().CurrentProcess()->VMManager();
+ const auto alloc_result = vm_manager.SetHeapSize(heap_size);
if (alloc_result.Failed()) {
return alloc_result.Code();
}
@@ -187,7 +184,7 @@ static ResultCode SetHeapSize(VAddr* heap_addr, u64 heap_size) {
return RESULT_SUCCESS;
}
-static ResultCode SetMemoryPermission(VAddr addr, u64 size, u32 prot) {
+static ResultCode SetMemoryPermission(Core::System& system, VAddr addr, u64 size, u32 prot) {
LOG_TRACE(Kernel_SVC, "called, addr=0x{:X}, size=0x{:X}, prot=0x{:X}", addr, size, prot);
if (!Common::Is4KBAligned(addr)) {
@@ -219,7 +216,7 @@ static ResultCode SetMemoryPermission(VAddr addr, u64 size, u32 prot) {
return ERR_INVALID_MEMORY_PERMISSIONS;
}
- auto* const current_process = Core::CurrentProcess();
+ auto* const current_process = system.Kernel().CurrentProcess();
auto& vm_manager = current_process->VMManager();
if (!vm_manager.IsWithinAddressSpace(addr, size)) {
@@ -244,7 +241,8 @@ static ResultCode SetMemoryPermission(VAddr addr, u64 size, u32 prot) {
return vm_manager.ReprotectRange(addr, size, converted_permissions);
}
-static ResultCode SetMemoryAttribute(VAddr address, u64 size, u32 mask, u32 attribute) {
+static ResultCode SetMemoryAttribute(Core::System& system, VAddr address, u64 size, u32 mask,
+ u32 attribute) {
LOG_DEBUG(Kernel_SVC,
"called, address=0x{:016X}, size=0x{:X}, mask=0x{:08X}, attribute=0x{:08X}", address,
size, mask, attribute);
@@ -282,7 +280,7 @@ static ResultCode SetMemoryAttribute(VAddr address, u64 size, u32 mask, u32 attr
return ERR_INVALID_COMBINATION;
}
- auto& vm_manager = Core::CurrentProcess()->VMManager();
+ auto& vm_manager = system.Kernel().CurrentProcess()->VMManager();
if (!vm_manager.IsWithinAddressSpace(address, size)) {
LOG_ERROR(Kernel_SVC,
"Given address (0x{:016X}) is outside the bounds of the address space.", address);
@@ -293,11 +291,11 @@ static ResultCode SetMemoryAttribute(VAddr address, u64 size, u32 mask, u32 attr
}
/// Maps a memory range into a different range.
-static ResultCode MapMemory(VAddr dst_addr, VAddr src_addr, u64 size) {
+static ResultCode MapMemory(Core::System& system, VAddr dst_addr, VAddr src_addr, u64 size) {
LOG_TRACE(Kernel_SVC, "called, dst_addr=0x{:X}, src_addr=0x{:X}, size=0x{:X}", dst_addr,
src_addr, size);
- auto& vm_manager = Core::CurrentProcess()->VMManager();
+ auto& vm_manager = system.Kernel().CurrentProcess()->VMManager();
const auto result = MapUnmapMemorySanityChecks(vm_manager, dst_addr, src_addr, size);
if (result.IsError()) {
@@ -308,11 +306,11 @@ static ResultCode MapMemory(VAddr dst_addr, VAddr src_addr, u64 size) {
}
/// Unmaps a region that was previously mapped with svcMapMemory
-static ResultCode UnmapMemory(VAddr dst_addr, VAddr src_addr, u64 size) {
+static ResultCode UnmapMemory(Core::System& system, VAddr dst_addr, VAddr src_addr, u64 size) {
LOG_TRACE(Kernel_SVC, "called, dst_addr=0x{:X}, src_addr=0x{:X}, size=0x{:X}", dst_addr,
src_addr, size);
- auto& vm_manager = Core::CurrentProcess()->VMManager();
+ auto& vm_manager = system.Kernel().CurrentProcess()->VMManager();
const auto result = MapUnmapMemorySanityChecks(vm_manager, dst_addr, src_addr, size);
if (result.IsError()) {
@@ -323,7 +321,8 @@ static ResultCode UnmapMemory(VAddr dst_addr, VAddr src_addr, u64 size) {
}
/// Connect to an OS service given the port name, returns the handle to the port to out
-static ResultCode ConnectToNamedPort(Handle* out_handle, VAddr port_name_address) {
+static ResultCode ConnectToNamedPort(Core::System& system, Handle* out_handle,
+ VAddr port_name_address) {
if (!Memory::IsValidVirtualAddress(port_name_address)) {
LOG_ERROR(Kernel_SVC,
"Port Name Address is not a valid virtual address, port_name_address=0x{:016X}",
@@ -342,8 +341,8 @@ static ResultCode ConnectToNamedPort(Handle* out_handle, VAddr port_name_address
LOG_TRACE(Kernel_SVC, "called port_name={}", port_name);
- auto& kernel = Core::System::GetInstance().Kernel();
- auto it = kernel.FindNamedPort(port_name);
+ auto& kernel = system.Kernel();
+ 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;
@@ -355,14 +354,14 @@ static ResultCode ConnectToNamedPort(Handle* out_handle, VAddr port_name_address
CASCADE_RESULT(client_session, client_port->Connect());
// Return the client session
- auto& handle_table = Core::CurrentProcess()->GetHandleTable();
+ auto& handle_table = kernel.CurrentProcess()->GetHandleTable();
CASCADE_RESULT(*out_handle, handle_table.Create(client_session));
return RESULT_SUCCESS;
}
/// Makes a blocking IPC call to an OS service.
-static ResultCode SendSyncRequest(Handle handle) {
- const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
+static ResultCode SendSyncRequest(Core::System& system, Handle handle) {
+ const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
SharedPtr<ClientSession> session = handle_table.Get<ClientSession>(handle);
if (!session) {
LOG_ERROR(Kernel_SVC, "called with invalid handle=0x{:08X}", handle);
@@ -371,18 +370,18 @@ static ResultCode SendSyncRequest(Handle handle) {
LOG_TRACE(Kernel_SVC, "called handle=0x{:08X}({})", handle, session->GetName());
- Core::System::GetInstance().PrepareReschedule();
+ system.PrepareReschedule();
// TODO(Subv): svcSendSyncRequest should put the caller thread to sleep while the server
// responds and cause a reschedule.
- return session->SendSyncRequest(GetCurrentThread());
+ return session->SendSyncRequest(system.CurrentScheduler().GetCurrentThread());
}
/// Get the ID for the specified thread.
-static ResultCode GetThreadId(u64* thread_id, Handle thread_handle) {
+static ResultCode GetThreadId(Core::System& system, u64* thread_id, Handle thread_handle) {
LOG_TRACE(Kernel_SVC, "called thread=0x{:08X}", thread_handle);
- const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
+ const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
const SharedPtr<Thread> thread = handle_table.Get<Thread>(thread_handle);
if (!thread) {
LOG_ERROR(Kernel_SVC, "Thread handle does not exist, handle=0x{:08X}", thread_handle);
@@ -394,10 +393,10 @@ static ResultCode GetThreadId(u64* thread_id, Handle thread_handle) {
}
/// Gets the ID of the specified process or a specified thread's owning process.
-static ResultCode GetProcessId(u64* process_id, Handle handle) {
+static ResultCode GetProcessId(Core::System& system, u64* process_id, Handle handle) {
LOG_DEBUG(Kernel_SVC, "called handle=0x{:08X}", handle);
- const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
+ const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
const SharedPtr<Process> process = handle_table.Get<Process>(handle);
if (process) {
*process_id = process->GetProcessID();
@@ -425,7 +424,7 @@ static ResultCode GetProcessId(u64* process_id, Handle handle) {
/// Default thread wakeup callback for WaitSynchronization
static bool DefaultThreadWakeupCallback(ThreadWakeupReason reason, SharedPtr<Thread> thread,
SharedPtr<WaitObject> object, std::size_t index) {
- ASSERT(thread->GetStatus() == ThreadStatus::WaitSynchAny);
+ ASSERT(thread->GetStatus() == ThreadStatus::WaitSynch);
if (reason == ThreadWakeupReason::Timeout) {
thread->SetWaitSynchronizationResult(RESULT_TIMEOUT);
@@ -439,8 +438,8 @@ static bool DefaultThreadWakeupCallback(ThreadWakeupReason reason, SharedPtr<Thr
};
/// Wait for the given handles to synchronize, timeout after the specified nanoseconds
-static ResultCode WaitSynchronization(Handle* index, VAddr handles_address, u64 handle_count,
- s64 nano_seconds) {
+static ResultCode WaitSynchronization(Core::System& system, Handle* 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);
@@ -459,11 +458,11 @@ static ResultCode WaitSynchronization(Handle* index, VAddr handles_address, u64
return ERR_OUT_OF_RANGE;
}
- auto* const thread = GetCurrentThread();
+ auto* const thread = system.CurrentScheduler().GetCurrentThread();
using ObjectPtr = Thread::ThreadWaitObjects::value_type;
Thread::ThreadWaitObjects objects(handle_count);
- const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
+ const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
for (u64 i = 0; i < handle_count; ++i) {
const Handle handle = Memory::Read32(handles_address + i * sizeof(Handle));
@@ -503,38 +502,36 @@ static ResultCode WaitSynchronization(Handle* index, VAddr handles_address, u64
}
thread->SetWaitObjects(std::move(objects));
- thread->SetStatus(ThreadStatus::WaitSynchAny);
+ thread->SetStatus(ThreadStatus::WaitSynch);
// Create an event to wake the thread up after the specified nanosecond delay has passed
thread->WakeAfterDelay(nano_seconds);
thread->SetWakeupCallback(DefaultThreadWakeupCallback);
- Core::System::GetInstance().CpuCore(thread->GetProcessorID()).PrepareReschedule();
+ system.CpuCore(thread->GetProcessorID()).PrepareReschedule();
return RESULT_TIMEOUT;
}
/// Resumes a thread waiting on WaitSynchronization
-static ResultCode CancelSynchronization(Handle thread_handle) {
+static ResultCode CancelSynchronization(Core::System& system, Handle thread_handle) {
LOG_TRACE(Kernel_SVC, "called thread=0x{:X}", thread_handle);
- const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
- const SharedPtr<Thread> thread = handle_table.Get<Thread>(thread_handle);
+ const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
+ SharedPtr<Thread> thread = handle_table.Get<Thread>(thread_handle);
if (!thread) {
LOG_ERROR(Kernel_SVC, "Thread handle does not exist, thread_handle=0x{:08X}",
thread_handle);
return ERR_INVALID_HANDLE;
}
- ASSERT(thread->GetStatus() == ThreadStatus::WaitSynchAny);
- thread->SetWaitSynchronizationResult(ERR_SYNCHRONIZATION_CANCELED);
- thread->ResumeFromWait();
+ thread->CancelWait();
return RESULT_SUCCESS;
}
/// Attempts to locks a mutex, creating it if it does not already exist
-static ResultCode ArbitrateLock(Handle holding_thread_handle, VAddr mutex_addr,
- Handle requesting_thread_handle) {
+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}",
@@ -551,13 +548,13 @@ static ResultCode ArbitrateLock(Handle holding_thread_handle, VAddr mutex_addr,
return ERR_INVALID_ADDRESS;
}
- auto& handle_table = Core::CurrentProcess()->GetHandleTable();
- return Mutex::TryAcquire(handle_table, mutex_addr, holding_thread_handle,
- requesting_thread_handle);
+ auto* const current_process = system.Kernel().CurrentProcess();
+ return current_process->GetMutex().TryAcquire(mutex_addr, holding_thread_handle,
+ requesting_thread_handle);
}
/// Unlock a mutex
-static ResultCode ArbitrateUnlock(VAddr mutex_addr) {
+static ResultCode ArbitrateUnlock(Core::System& system, VAddr mutex_addr) {
LOG_TRACE(Kernel_SVC, "called mutex_addr=0x{:X}", mutex_addr);
if (Memory::IsKernelVirtualAddress(mutex_addr)) {
@@ -571,7 +568,8 @@ static ResultCode ArbitrateUnlock(VAddr mutex_addr) {
return ERR_INVALID_ADDRESS;
}
- return Mutex::Release(mutex_addr);
+ auto* const current_process = system.Kernel().CurrentProcess();
+ return current_process->GetMutex().Release(mutex_addr);
}
enum class BreakType : u32 {
@@ -593,7 +591,7 @@ struct BreakReason {
};
/// Break program execution
-static void Break(u32 reason, u64 info1, u64 info2) {
+static void Break(Core::System& system, u32 reason, u64 info1, u64 info2) {
BreakReason break_reason{reason};
bool has_dumped_buffer{};
@@ -671,22 +669,24 @@ static void Break(u32 reason, u64 info1, u64 info2) {
Debug_Emulated,
"Emulated program broke execution! reason=0x{:016X}, info1=0x{:016X}, info2=0x{:016X}",
reason, info1, info2);
+
handle_debug_buffer(info1, info2);
- Core::System::GetInstance()
- .ArmInterface(static_cast<std::size_t>(GetCurrentThread()->GetProcessorID()))
- .LogBacktrace();
+
+ auto* const current_thread = system.CurrentScheduler().GetCurrentThread();
+ const auto thread_processor_id = current_thread->GetProcessorID();
+ system.ArmInterface(static_cast<std::size_t>(thread_processor_id)).LogBacktrace();
ASSERT(false);
- Core::CurrentProcess()->PrepareForTermination();
+ system.Kernel().CurrentProcess()->PrepareForTermination();
// Kill the current thread
- GetCurrentThread()->Stop();
- Core::System::GetInstance().PrepareReschedule();
+ current_thread->Stop();
+ system.PrepareReschedule();
}
}
/// Used to output a message on a debug hardware unit - does nothing on a retail unit
-static void OutputDebugString(VAddr address, u64 len) {
+static void OutputDebugString([[maybe_unused]] Core::System& system, VAddr address, u64 len) {
if (len == 0) {
return;
}
@@ -697,7 +697,8 @@ static void OutputDebugString(VAddr address, u64 len) {
}
/// Gets system/memory information for the current process
-static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id) {
+static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 handle,
+ u64 info_sub_id) {
LOG_TRACE(Kernel_SVC, "called info_id=0x{:X}, info_sub_id=0x{:X}, handle=0x{:08X}", info_id,
info_sub_id, handle);
@@ -709,13 +710,13 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id)
MapRegionSize = 3,
HeapRegionBaseAddr = 4,
HeapRegionSize = 5,
- TotalMemoryUsage = 6,
- TotalHeapUsage = 7,
+ TotalPhysicalMemoryAvailable = 6,
+ TotalPhysicalMemoryUsed = 7,
IsCurrentProcessBeingDebugged = 8,
RegisterResourceLimit = 9,
IdleTickCount = 10,
RandomEntropy = 11,
- PerformanceCounter = 0xF0000002,
+ ThreadTickCount = 0xF0000002,
// 2.0.0+
ASLRRegionBaseAddr = 12,
ASLRRegionSize = 13,
@@ -729,7 +730,9 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id)
PrivilegedProcessId = 19,
// 5.0.0+
UserExceptionContextAddr = 20,
- ThreadTickCount = 0xF0000002,
+ // 6.0.0+
+ TotalPhysicalMemoryAvailableWithoutMmHeap = 21,
+ TotalPhysicalMemoryUsedWithoutMmHeap = 22,
};
const auto info_id_type = static_cast<GetInfoType>(info_id);
@@ -745,17 +748,20 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id)
case GetInfoType::ASLRRegionSize:
case GetInfoType::NewMapRegionBaseAddr:
case GetInfoType::NewMapRegionSize:
- case GetInfoType::TotalMemoryUsage:
- case GetInfoType::TotalHeapUsage:
+ case GetInfoType::TotalPhysicalMemoryAvailable:
+ case GetInfoType::TotalPhysicalMemoryUsed:
case GetInfoType::IsVirtualAddressMemoryEnabled:
case GetInfoType::PersonalMmHeapUsage:
case GetInfoType::TitleId:
- case GetInfoType::UserExceptionContextAddr: {
+ case GetInfoType::UserExceptionContextAddr:
+ case GetInfoType::TotalPhysicalMemoryAvailableWithoutMmHeap:
+ case GetInfoType::TotalPhysicalMemoryUsedWithoutMmHeap: {
if (info_sub_id != 0) {
return ERR_INVALID_ENUM_VALUE;
}
- const auto& current_process_handle_table = Core::CurrentProcess()->GetHandleTable();
+ const auto& current_process_handle_table =
+ system.Kernel().CurrentProcess()->GetHandleTable();
const auto process = current_process_handle_table.Get<Process>(static_cast<Handle>(handle));
if (!process) {
return ERR_INVALID_HANDLE;
@@ -802,12 +808,12 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id)
*result = process->VMManager().GetNewMapRegionSize();
return RESULT_SUCCESS;
- case GetInfoType::TotalMemoryUsage:
- *result = process->VMManager().GetTotalMemoryUsage();
+ case GetInfoType::TotalPhysicalMemoryAvailable:
+ *result = process->GetTotalPhysicalMemoryAvailable();
return RESULT_SUCCESS;
- case GetInfoType::TotalHeapUsage:
- *result = process->VMManager().GetTotalHeapUsage();
+ case GetInfoType::TotalPhysicalMemoryUsed:
+ *result = process->GetTotalPhysicalMemoryUsed();
return RESULT_SUCCESS;
case GetInfoType::IsVirtualAddressMemoryEnabled:
@@ -824,6 +830,14 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id)
*result = 0;
return RESULT_SUCCESS;
+ case GetInfoType::TotalPhysicalMemoryAvailableWithoutMmHeap:
+ *result = process->GetTotalPhysicalMemoryAvailable();
+ return RESULT_SUCCESS;
+
+ case GetInfoType::TotalPhysicalMemoryUsedWithoutMmHeap:
+ *result = process->GetTotalPhysicalMemoryUsedWithoutMmHeap();
+ return RESULT_SUCCESS;
+
default:
break;
}
@@ -845,7 +859,7 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id)
return ERR_INVALID_COMBINATION;
}
- Process* const current_process = Core::CurrentProcess();
+ Process* const current_process = system.Kernel().CurrentProcess();
HandleTable& handle_table = current_process->GetHandleTable();
const auto resource_limit = current_process->GetResourceLimit();
if (!resource_limit) {
@@ -876,7 +890,7 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id)
return ERR_INVALID_COMBINATION;
}
- *result = Core::CurrentProcess()->GetRandomEntropy(info_sub_id);
+ *result = system.Kernel().CurrentProcess()->GetRandomEntropy(info_sub_id);
return RESULT_SUCCESS;
case GetInfoType::PrivilegedProcessId:
@@ -893,15 +907,14 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id)
return ERR_INVALID_COMBINATION;
}
- const auto thread =
- Core::CurrentProcess()->GetHandleTable().Get<Thread>(static_cast<Handle>(handle));
+ const auto thread = system.Kernel().CurrentProcess()->GetHandleTable().Get<Thread>(
+ 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;
}
- const auto& system = Core::System::GetInstance();
const auto& core_timing = system.CoreTiming();
const auto& scheduler = system.CurrentScheduler();
const auto* const current_thread = scheduler.GetCurrentThread();
@@ -928,13 +941,13 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id)
}
/// Sets the thread activity
-static ResultCode SetThreadActivity(Handle handle, u32 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;
}
- const auto* current_process = Core::CurrentProcess();
+ const auto* current_process = system.Kernel().CurrentProcess();
const SharedPtr<Thread> thread = current_process->GetHandleTable().Get<Thread>(handle);
if (!thread) {
LOG_ERROR(Kernel_SVC, "Thread handle does not exist, handle=0x{:08X}", handle);
@@ -951,7 +964,7 @@ static ResultCode SetThreadActivity(Handle handle, u32 activity) {
return ERR_INVALID_HANDLE;
}
- if (thread == GetCurrentThread()) {
+ if (thread == system.CurrentScheduler().GetCurrentThread()) {
LOG_ERROR(Kernel_SVC, "The thread handle specified is the current running thread");
return ERR_BUSY;
}
@@ -961,10 +974,10 @@ static ResultCode SetThreadActivity(Handle handle, u32 activity) {
}
/// Gets the thread context
-static ResultCode GetThreadContext(VAddr thread_context, Handle handle) {
+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);
- const auto* current_process = Core::CurrentProcess();
+ const auto* current_process = system.Kernel().CurrentProcess();
const SharedPtr<Thread> thread = current_process->GetHandleTable().Get<Thread>(handle);
if (!thread) {
LOG_ERROR(Kernel_SVC, "Thread handle does not exist, handle=0x{:08X}", handle);
@@ -981,7 +994,7 @@ static ResultCode GetThreadContext(VAddr thread_context, Handle handle) {
return ERR_INVALID_HANDLE;
}
- if (thread == GetCurrentThread()) {
+ if (thread == system.CurrentScheduler().GetCurrentThread()) {
LOG_ERROR(Kernel_SVC, "The thread handle specified is the current running thread");
return ERR_BUSY;
}
@@ -1002,10 +1015,10 @@ static ResultCode GetThreadContext(VAddr thread_context, Handle handle) {
}
/// Gets the priority for the specified thread
-static ResultCode GetThreadPriority(u32* priority, Handle handle) {
+static ResultCode GetThreadPriority(Core::System& system, u32* priority, Handle handle) {
LOG_TRACE(Kernel_SVC, "called");
- const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
+ const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
const SharedPtr<Thread> thread = handle_table.Get<Thread>(handle);
if (!thread) {
LOG_ERROR(Kernel_SVC, "Thread handle does not exist, handle=0x{:08X}", handle);
@@ -1017,7 +1030,7 @@ static ResultCode GetThreadPriority(u32* priority, Handle handle) {
}
/// Sets the priority for the specified thread
-static ResultCode SetThreadPriority(Handle handle, u32 priority) {
+static ResultCode SetThreadPriority(Core::System& system, Handle handle, u32 priority) {
LOG_TRACE(Kernel_SVC, "called");
if (priority > THREADPRIO_LOWEST) {
@@ -1028,7 +1041,7 @@ static ResultCode SetThreadPriority(Handle handle, u32 priority) {
return ERR_INVALID_THREAD_PRIORITY;
}
- const auto* const current_process = Core::CurrentProcess();
+ const auto* const current_process = system.Kernel().CurrentProcess();
SharedPtr<Thread> thread = current_process->GetHandleTable().Get<Thread>(handle);
if (!thread) {
@@ -1038,18 +1051,18 @@ static ResultCode SetThreadPriority(Handle handle, u32 priority) {
thread->SetPriority(priority);
- Core::System::GetInstance().CpuCore(thread->GetProcessorID()).PrepareReschedule();
+ system.CpuCore(thread->GetProcessorID()).PrepareReschedule();
return RESULT_SUCCESS;
}
/// Get which CPU core is executing the current thread
-static u32 GetCurrentProcessorNumber() {
+static u32 GetCurrentProcessorNumber(Core::System& system) {
LOG_TRACE(Kernel_SVC, "called");
- return GetCurrentThread()->GetProcessorID();
+ return system.CurrentScheduler().GetCurrentThread()->GetProcessorID();
}
-static ResultCode MapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 size,
- u32 permissions) {
+static ResultCode MapSharedMemory(Core::System& system, Handle shared_memory_handle, VAddr addr,
+ u64 size, u32 permissions) {
LOG_TRACE(Kernel_SVC,
"called, shared_memory_handle=0x{:X}, addr=0x{:X}, size=0x{:X}, permissions=0x{:08X}",
shared_memory_handle, addr, size, permissions);
@@ -1083,7 +1096,7 @@ static ResultCode MapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 s
return ERR_INVALID_MEMORY_PERMISSIONS;
}
- auto* const current_process = Core::CurrentProcess();
+ auto* const current_process = system.Kernel().CurrentProcess();
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}",
@@ -1101,7 +1114,8 @@ static ResultCode MapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 s
return shared_memory->Map(*current_process, addr, permissions_type, MemoryPermission::DontCare);
}
-static ResultCode UnmapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 size) {
+static ResultCode UnmapSharedMemory(Core::System& system, Handle shared_memory_handle, VAddr addr,
+ u64 size) {
LOG_WARNING(Kernel_SVC, "called, shared_memory_handle=0x{:08X}, addr=0x{:X}, size=0x{:X}",
shared_memory_handle, addr, size);
@@ -1126,7 +1140,7 @@ static ResultCode UnmapSharedMemory(Handle shared_memory_handle, VAddr addr, u64
return ERR_INVALID_ADDRESS_STATE;
}
- auto* const current_process = Core::CurrentProcess();
+ auto* const current_process = system.Kernel().CurrentProcess();
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}",
@@ -1141,13 +1155,14 @@ static ResultCode UnmapSharedMemory(Handle shared_memory_handle, VAddr addr, u64
return ERR_INVALID_MEMORY_RANGE;
}
- return shared_memory->Unmap(*current_process, addr);
+ return shared_memory->Unmap(*current_process, addr, size);
}
-static ResultCode QueryProcessMemory(VAddr memory_info_address, VAddr page_info_address,
- Handle process_handle, VAddr address) {
+static ResultCode QueryProcessMemory(Core::System& system, VAddr memory_info_address,
+ VAddr page_info_address, Handle process_handle,
+ VAddr address) {
LOG_TRACE(Kernel_SVC, "called process=0x{:08X} address={:X}", process_handle, address);
- const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
+ const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
SharedPtr<Process> process = handle_table.Get<Process>(process_handle);
if (!process) {
LOG_ERROR(Kernel_SVC, "Process handle does not exist, process_handle=0x{:08X}",
@@ -1173,20 +1188,156 @@ static ResultCode QueryProcessMemory(VAddr memory_info_address, VAddr page_info_
return RESULT_SUCCESS;
}
-static ResultCode QueryMemory(VAddr memory_info_address, VAddr page_info_address,
- VAddr query_address) {
+static ResultCode QueryMemory(Core::System& system, VAddr memory_info_address,
+ VAddr page_info_address, VAddr query_address) {
LOG_TRACE(Kernel_SVC,
"called, memory_info_address=0x{:016X}, page_info_address=0x{:016X}, "
"query_address=0x{:016X}",
memory_info_address, page_info_address, query_address);
- return QueryProcessMemory(memory_info_address, page_info_address, CurrentProcess,
+ return QueryProcessMemory(system, memory_info_address, page_info_address, CurrentProcess,
query_address);
}
+static ResultCode MapProcessCodeMemory(Core::System& system, Handle process_handle, u64 dst_address,
+ u64 src_address, u64 size) {
+ LOG_DEBUG(Kernel_SVC,
+ "called. process_handle=0x{:08X}, dst_address=0x{:016X}, "
+ "src_address=0x{:016X}, size=0x{:016X}",
+ process_handle, dst_address, src_address, size);
+
+ 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;
+ }
+
+ 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;
+ }
+
+ 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;
+ }
+
+ if (!IsValidAddressRange(dst_address, size)) {
+ LOG_ERROR(Kernel_SVC,
+ "Destination address range overflows the address space (dst_address=0x{:016X}, "
+ "size=0x{:016X}).",
+ dst_address, size);
+ return ERR_INVALID_ADDRESS_STATE;
+ }
+
+ if (!IsValidAddressRange(src_address, size)) {
+ LOG_ERROR(Kernel_SVC,
+ "Source address range overflows the address space (src_address=0x{:016X}, "
+ "size=0x{:016X}).",
+ src_address, size);
+ return ERR_INVALID_ADDRESS_STATE;
+ }
+
+ const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
+ auto process = handle_table.Get<Process>(process_handle);
+ if (!process) {
+ LOG_ERROR(Kernel_SVC, "Invalid process handle specified (handle=0x{:08X}).",
+ process_handle);
+ return ERR_INVALID_HANDLE;
+ }
+
+ auto& vm_manager = process->VMManager();
+ if (!vm_manager.IsWithinAddressSpace(src_address, size)) {
+ LOG_ERROR(Kernel_SVC,
+ "Source address range is not within the address space (src_address=0x{:016X}, "
+ "size=0x{:016X}).",
+ src_address, size);
+ return ERR_INVALID_ADDRESS_STATE;
+ }
+
+ if (!vm_manager.IsWithinASLRRegion(dst_address, size)) {
+ LOG_ERROR(Kernel_SVC,
+ "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 vm_manager.MapCodeMemory(dst_address, src_address, size);
+}
+
+static ResultCode UnmapProcessCodeMemory(Core::System& system, Handle process_handle,
+ u64 dst_address, u64 src_address, u64 size) {
+ LOG_DEBUG(Kernel_SVC,
+ "called. process_handle=0x{:08X}, dst_address=0x{:016X}, src_address=0x{:016X}, "
+ "size=0x{:016X}",
+ process_handle, dst_address, src_address, size);
+
+ 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;
+ }
+
+ 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;
+ }
+
+ 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;
+ }
+
+ if (!IsValidAddressRange(dst_address, size)) {
+ LOG_ERROR(Kernel_SVC,
+ "Destination address range overflows the address space (dst_address=0x{:016X}, "
+ "size=0x{:016X}).",
+ dst_address, size);
+ return ERR_INVALID_ADDRESS_STATE;
+ }
+
+ if (!IsValidAddressRange(src_address, size)) {
+ LOG_ERROR(Kernel_SVC,
+ "Source address range overflows the address space (src_address=0x{:016X}, "
+ "size=0x{:016X}).",
+ src_address, size);
+ return ERR_INVALID_ADDRESS_STATE;
+ }
+
+ const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
+ auto process = handle_table.Get<Process>(process_handle);
+ if (!process) {
+ LOG_ERROR(Kernel_SVC, "Invalid process handle specified (handle=0x{:08X}).",
+ process_handle);
+ return ERR_INVALID_HANDLE;
+ }
+
+ auto& vm_manager = process->VMManager();
+ if (!vm_manager.IsWithinAddressSpace(src_address, size)) {
+ LOG_ERROR(Kernel_SVC,
+ "Source address range is not within the address space (src_address=0x{:016X}, "
+ "size=0x{:016X}).",
+ src_address, size);
+ return ERR_INVALID_ADDRESS_STATE;
+ }
+
+ if (!vm_manager.IsWithinASLRRegion(dst_address, size)) {
+ LOG_ERROR(Kernel_SVC,
+ "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 vm_manager.UnmapCodeMemory(dst_address, src_address, size);
+}
+
/// Exits the current process
-static void ExitProcess() {
- auto* current_process = Core::CurrentProcess();
+static void ExitProcess(Core::System& system) {
+ auto* current_process = system.Kernel().CurrentProcess();
LOG_INFO(Kernel_SVC, "Process {} exiting", current_process->GetProcessID());
ASSERT_MSG(current_process->GetStatus() == ProcessStatus::Running,
@@ -1195,20 +1346,20 @@ static void ExitProcess() {
current_process->PrepareForTermination();
// Kill the current thread
- GetCurrentThread()->Stop();
+ system.CurrentScheduler().GetCurrentThread()->Stop();
- Core::System::GetInstance().PrepareReschedule();
+ system.PrepareReschedule();
}
/// Creates a new thread
-static ResultCode CreateThread(Handle* out_handle, VAddr entry_point, u64 arg, VAddr stack_top,
- u32 priority, s32 processor_id) {
- LOG_TRACE(Kernel_SVC,
+static ResultCode CreateThread(Core::System& system, Handle* out_handle, VAddr entry_point, u64 arg,
+ VAddr stack_top, u32 priority, s32 processor_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);
- auto* const current_process = Core::CurrentProcess();
+ auto* const current_process = system.Kernel().CurrentProcess();
if (processor_id == THREADPROCESSORID_IDEAL) {
// Set the target CPU to the one specified by the process.
@@ -1239,31 +1390,33 @@ static ResultCode CreateThread(Handle* out_handle, VAddr entry_point, u64 arg, V
return ERR_INVALID_THREAD_PRIORITY;
}
- const std::string name = fmt::format("thread-{:X}", entry_point);
- auto& kernel = Core::System::GetInstance().Kernel();
+ auto& kernel = system.Kernel();
CASCADE_RESULT(SharedPtr<Thread> thread,
- Thread::Create(kernel, name, entry_point, priority, arg, processor_id, stack_top,
+ Thread::Create(kernel, "", entry_point, priority, arg, processor_id, stack_top,
*current_process));
- const auto new_guest_handle = current_process->GetHandleTable().Create(thread);
- if (new_guest_handle.Failed()) {
+ const auto new_thread_handle = current_process->GetHandleTable().Create(thread);
+ if (new_thread_handle.Failed()) {
LOG_ERROR(Kernel_SVC, "Failed to create handle with error=0x{:X}",
- new_guest_handle.Code().raw);
- return new_guest_handle.Code();
+ new_thread_handle.Code().raw);
+ return new_thread_handle.Code();
}
- thread->SetGuestHandle(*new_guest_handle);
- *out_handle = *new_guest_handle;
+ *out_handle = *new_thread_handle;
+
+ // Set the thread name for debugging purposes.
+ thread->SetName(
+ fmt::format("thread[entry_point={:X}, handle={:X}]", entry_point, *new_thread_handle));
- Core::System::GetInstance().CpuCore(thread->GetProcessorID()).PrepareReschedule();
+ system.CpuCore(thread->GetProcessorID()).PrepareReschedule();
return RESULT_SUCCESS;
}
/// Starts the thread for the provided handle
-static ResultCode StartThread(Handle thread_handle) {
- LOG_TRACE(Kernel_SVC, "called thread=0x{:08X}", thread_handle);
+static ResultCode StartThread(Core::System& system, Handle thread_handle) {
+ LOG_DEBUG(Kernel_SVC, "called thread=0x{:08X}", thread_handle);
- const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
+ const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
const SharedPtr<Thread> thread = handle_table.Get<Thread>(thread_handle);
if (!thread) {
LOG_ERROR(Kernel_SVC, "Thread handle does not exist, thread_handle=0x{:08X}",
@@ -1276,23 +1429,25 @@ static ResultCode StartThread(Handle thread_handle) {
thread->ResumeFromWait();
if (thread->GetStatus() == ThreadStatus::Ready) {
- Core::System::GetInstance().CpuCore(thread->GetProcessorID()).PrepareReschedule();
+ system.CpuCore(thread->GetProcessorID()).PrepareReschedule();
}
return RESULT_SUCCESS;
}
/// Called when a thread exits
-static void ExitThread() {
- LOG_TRACE(Kernel_SVC, "called, pc=0x{:08X}", Core::CurrentArmInterface().GetPC());
+static void ExitThread(Core::System& system) {
+ LOG_DEBUG(Kernel_SVC, "called, pc=0x{:08X}", system.CurrentArmInterface().GetPC());
- ExitCurrentThread();
- Core::System::GetInstance().PrepareReschedule();
+ auto* const current_thread = system.CurrentScheduler().GetCurrentThread();
+ current_thread->Stop();
+ system.CurrentScheduler().RemoveThread(current_thread);
+ system.PrepareReschedule();
}
/// Sleep the current thread
-static void SleepThread(s64 nanoseconds) {
- LOG_TRACE(Kernel_SVC, "called nanoseconds={}", nanoseconds);
+static void SleepThread(Core::System& system, s64 nanoseconds) {
+ LOG_DEBUG(Kernel_SVC, "called nanoseconds={}", nanoseconds);
enum class SleepType : s64 {
YieldWithoutLoadBalancing = 0,
@@ -1300,72 +1455,91 @@ static void SleepThread(s64 nanoseconds) {
YieldAndWaitForLoadBalancing = -2,
};
+ auto& scheduler = system.CurrentScheduler();
+ auto* const current_thread = scheduler.GetCurrentThread();
+
if (nanoseconds <= 0) {
- auto& scheduler{Core::System::GetInstance().CurrentScheduler()};
switch (static_cast<SleepType>(nanoseconds)) {
case SleepType::YieldWithoutLoadBalancing:
- scheduler.YieldWithoutLoadBalancing(GetCurrentThread());
+ scheduler.YieldWithoutLoadBalancing(current_thread);
break;
case SleepType::YieldWithLoadBalancing:
- scheduler.YieldWithLoadBalancing(GetCurrentThread());
+ scheduler.YieldWithLoadBalancing(current_thread);
break;
case SleepType::YieldAndWaitForLoadBalancing:
- scheduler.YieldAndWaitForLoadBalancing(GetCurrentThread());
+ scheduler.YieldAndWaitForLoadBalancing(current_thread);
break;
default:
UNREACHABLE_MSG("Unimplemented sleep yield type '{:016X}'!", nanoseconds);
}
} else {
- // Sleep current thread and check for next thread to schedule
- WaitCurrentThread_Sleep();
-
- // Create an event to wake the thread up after the specified nanosecond delay has passed
- GetCurrentThread()->WakeAfterDelay(nanoseconds);
+ current_thread->Sleep(nanoseconds);
}
// Reschedule all CPU cores
- for (std::size_t i = 0; i < Core::NUM_CPU_CORES; ++i)
- Core::System::GetInstance().CpuCore(i).PrepareReschedule();
+ for (std::size_t i = 0; i < Core::NUM_CPU_CORES; ++i) {
+ system.CpuCore(i).PrepareReschedule();
+ }
}
/// Wait process wide key atomic
-static ResultCode WaitProcessWideKeyAtomic(VAddr mutex_addr, VAddr condition_variable_addr,
- Handle thread_handle, s64 nano_seconds) {
+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);
- const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
+ if (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;
+ }
+
+ auto* const current_process = system.Kernel().CurrentProcess();
+ const auto& handle_table = current_process->GetHandleTable();
SharedPtr<Thread> thread = handle_table.Get<Thread>(thread_handle);
ASSERT(thread);
- CASCADE_CODE(Mutex::Release(mutex_addr));
+ const auto release_result = current_process->GetMutex().Release(mutex_addr);
+ if (release_result.IsError()) {
+ return release_result;
+ }
- SharedPtr<Thread> current_thread = GetCurrentThread();
+ SharedPtr<Thread> current_thread = system.CurrentScheduler().GetCurrentThread();
current_thread->SetCondVarWaitAddress(condition_variable_addr);
current_thread->SetMutexWaitAddress(mutex_addr);
current_thread->SetWaitHandle(thread_handle);
- current_thread->SetStatus(ThreadStatus::WaitMutex);
+ current_thread->SetStatus(ThreadStatus::WaitCondVar);
current_thread->InvalidateWakeupCallback();
current_thread->WakeAfterDelay(nano_seconds);
// Note: Deliberately don't attempt to inherit the lock owner's priority.
- Core::System::GetInstance().CpuCore(current_thread->GetProcessorID()).PrepareReschedule();
+ system.CpuCore(current_thread->GetProcessorID()).PrepareReschedule();
return RESULT_SUCCESS;
}
/// Signal process wide key
-static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target) {
+static ResultCode 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);
- const auto RetrieveWaitingThreads = [](std::size_t core_index,
- std::vector<SharedPtr<Thread>>& waiting_threads,
- VAddr condvar_addr) {
- const auto& scheduler = Core::System::GetInstance().Scheduler(core_index);
+ const auto RetrieveWaitingThreads = [&system](std::size_t core_index,
+ std::vector<SharedPtr<Thread>>& waiting_threads,
+ VAddr condvar_addr) {
+ const auto& scheduler = system.Scheduler(core_index);
const auto& thread_list = scheduler.GetThreadList();
for (const auto& thread : thread_list) {
@@ -1390,10 +1564,10 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target
// them all.
std::size_t last = waiting_threads.size();
if (target != -1)
- last = target;
+ last = std::min(waiting_threads.size(), static_cast<std::size_t>(target));
// If there are no threads waiting on this condition variable, just exit
- if (last > waiting_threads.size())
+ if (last == 0)
return RESULT_SUCCESS;
for (std::size_t index = 0; index < last; ++index) {
@@ -1401,9 +1575,11 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target
ASSERT(thread->GetCondVarWaitAddress() == condition_variable_addr);
- std::size_t current_core = Core::System::GetInstance().CurrentCoreIndex();
+ // liberate Cond Var Thread.
+ thread->SetCondVarWaitAddress(0);
- auto& monitor = Core::System::GetInstance().Monitor();
+ const std::size_t current_core = system.CurrentCoreIndex();
+ auto& monitor = system.Monitor();
// Atomically read the value of the mutex.
u32 mutex_val = 0;
@@ -1419,10 +1595,9 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target
}
} while (!monitor.ExclusiveWrite32(current_core, thread->GetMutexWaitAddress(),
thread->GetWaitHandle()));
-
if (mutex_val == 0) {
// We were able to acquire the mutex, resume this thread.
- ASSERT(thread->GetStatus() == ThreadStatus::WaitMutex);
+ ASSERT(thread->GetStatus() == ThreadStatus::WaitCondVar);
thread->ResumeFromWait();
auto* const lock_owner = thread->GetLockOwner();
@@ -1432,8 +1607,8 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target
thread->SetLockOwner(nullptr);
thread->SetMutexWaitAddress(0);
- thread->SetCondVarWaitAddress(0);
thread->SetWaitHandle(0);
+ system.CpuCore(thread->GetProcessorID()).PrepareReschedule();
} else {
// Atomically signal that the mutex now has a waiting thread.
do {
@@ -1449,15 +1624,14 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target
// 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 = Core::CurrentProcess()->GetHandleTable();
+ const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
auto owner = handle_table.Get<Thread>(owner_handle);
ASSERT(owner);
- ASSERT(thread->GetStatus() == ThreadStatus::WaitMutex);
+ ASSERT(thread->GetStatus() == ThreadStatus::WaitCondVar);
thread->InvalidateWakeupCallback();
+ thread->SetStatus(ThreadStatus::WaitMutex);
owner->AddMutexWaiter(thread);
-
- Core::System::GetInstance().CpuCore(thread->GetProcessorID()).PrepareReschedule();
}
}
@@ -1465,75 +1639,56 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target
}
// Wait for an address (via Address Arbiter)
-static ResultCode WaitForAddress(VAddr address, u32 type, s32 value, s64 timeout) {
+static ResultCode WaitForAddress(Core::System& system, VAddr address, u32 type, s32 value,
+ s64 timeout) {
LOG_WARNING(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 (Memory::IsKernelVirtualAddress(address)) {
LOG_ERROR(Kernel_SVC, "Address is a kernel virtual address, address={:016X}", address);
return ERR_INVALID_ADDRESS_STATE;
}
+
// 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;
}
- auto& address_arbiter = Core::System::GetInstance().Kernel().AddressArbiter();
- switch (static_cast<AddressArbiter::ArbitrationType>(type)) {
- case AddressArbiter::ArbitrationType::WaitIfLessThan:
- return address_arbiter.WaitForAddressIfLessThan(address, value, timeout, false);
- case AddressArbiter::ArbitrationType::DecrementAndWaitIfLessThan:
- return address_arbiter.WaitForAddressIfLessThan(address, value, timeout, true);
- case AddressArbiter::ArbitrationType::WaitIfEqual:
- return address_arbiter.WaitForAddressIfEqual(address, value, timeout);
- default:
- LOG_ERROR(Kernel_SVC,
- "Invalid arbitration type, expected WaitIfLessThan, DecrementAndWaitIfLessThan "
- "or WaitIfEqual but got {}",
- type);
- return ERR_INVALID_ENUM_VALUE;
- }
+ const auto arbitration_type = static_cast<AddressArbiter::ArbitrationType>(type);
+ auto& address_arbiter = system.Kernel().CurrentProcess()->GetAddressArbiter();
+ return address_arbiter.WaitForAddress(address, arbitration_type, value, timeout);
}
// Signals to an address (via Address Arbiter)
-static ResultCode SignalToAddress(VAddr address, u32 type, s32 value, s32 num_to_wake) {
+static ResultCode SignalToAddress(Core::System& system, VAddr address, u32 type, s32 value,
+ s32 num_to_wake) {
LOG_WARNING(Kernel_SVC, "called, address=0x{:X}, type=0x{:X}, value=0x{:X}, num_to_wake=0x{:X}",
address, type, value, num_to_wake);
+
// If the passed address is a kernel virtual address, return invalid memory state.
if (Memory::IsKernelVirtualAddress(address)) {
LOG_ERROR(Kernel_SVC, "Address is a kernel virtual address, address={:016X}", address);
return ERR_INVALID_ADDRESS_STATE;
}
+
// 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;
}
- auto& address_arbiter = Core::System::GetInstance().Kernel().AddressArbiter();
- switch (static_cast<AddressArbiter::SignalType>(type)) {
- case AddressArbiter::SignalType::Signal:
- return address_arbiter.SignalToAddress(address, num_to_wake);
- case AddressArbiter::SignalType::IncrementAndSignalIfEqual:
- return address_arbiter.IncrementAndSignalToAddressIfEqual(address, value, num_to_wake);
- case AddressArbiter::SignalType::ModifyByWaitingCountAndSignalIfEqual:
- return address_arbiter.ModifyByWaitingCountAndSignalToAddressIfEqual(address, value,
- num_to_wake);
- default:
- LOG_ERROR(Kernel_SVC,
- "Invalid signal type, expected Signal, IncrementAndSignalIfEqual "
- "or ModifyByWaitingCountAndSignalIfEqual but got {}",
- type);
- return ERR_INVALID_ENUM_VALUE;
- }
+ 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);
}
/// This returns the total CPU ticks elapsed since the CPU was powered-on
-static u64 GetSystemTick() {
+static u64 GetSystemTick(Core::System& system) {
LOG_TRACE(Kernel_SVC, "called");
- auto& core_timing = Core::System::GetInstance().CoreTiming();
+ auto& core_timing = system.CoreTiming();
const u64 result{core_timing.GetTicks()};
// Advance time to defeat dumb games that busy-wait for the frame to end.
@@ -1543,18 +1698,18 @@ static u64 GetSystemTick() {
}
/// Close a handle
-static ResultCode CloseHandle(Handle handle) {
+static ResultCode CloseHandle(Core::System& system, Handle handle) {
LOG_TRACE(Kernel_SVC, "Closing handle 0x{:08X}", handle);
- auto& handle_table = Core::CurrentProcess()->GetHandleTable();
+ auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
return handle_table.Close(handle);
}
/// Clears the signaled state of an event or process.
-static ResultCode ResetSignal(Handle handle) {
+static ResultCode ResetSignal(Core::System& system, Handle handle) {
LOG_DEBUG(Kernel_SVC, "called handle 0x{:08X}", handle);
- const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
+ const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
auto event = handle_table.Get<ReadableEvent>(handle);
if (event) {
@@ -1571,7 +1726,8 @@ static ResultCode ResetSignal(Handle handle) {
}
/// Creates a TransferMemory object
-static ResultCode CreateTransferMemory(Handle* handle, VAddr addr, u64 size, u32 permissions) {
+static ResultCode CreateTransferMemory(Core::System& system, Handle* handle, VAddr addr, u64 size,
+ u32 permissions) {
LOG_DEBUG(Kernel_SVC, "called addr=0x{:X}, size=0x{:X}, perms=0x{:08X}", addr, size,
permissions);
@@ -1599,19 +1755,129 @@ static ResultCode CreateTransferMemory(Handle* handle, VAddr addr, u64 size, u32
return ERR_INVALID_MEMORY_PERMISSIONS;
}
- auto& kernel = Core::System::GetInstance().Kernel();
- auto process = kernel.CurrentProcess();
- auto& handle_table = process->GetHandleTable();
- const auto shared_mem_handle = SharedMemory::Create(kernel, process, size, perms, perms, addr);
+ auto& kernel = system.Kernel();
+ auto transfer_mem_handle = TransferMemory::Create(kernel, addr, size, perms);
- CASCADE_RESULT(*handle, handle_table.Create(shared_mem_handle));
+ auto& handle_table = kernel.CurrentProcess()->GetHandleTable();
+ const auto result = handle_table.Create(std::move(transfer_mem_handle));
+ if (result.Failed()) {
+ return result.Code();
+ }
+
+ *handle = *result;
return RESULT_SUCCESS;
}
-static ResultCode GetThreadCoreMask(Handle thread_handle, u32* core, u64* mask) {
+static ResultCode MapTransferMemory(Core::System& system, Handle handle, VAddr address, u64 size,
+ u32 permission_raw) {
+ LOG_DEBUG(Kernel_SVC,
+ "called. handle=0x{:08X}, address=0x{:016X}, size=0x{:016X}, permissions=0x{:08X}",
+ handle, address, size, permission_raw);
+
+ if (!Common::Is4KBAligned(address)) {
+ LOG_ERROR(Kernel_SVC, "Transfer memory addresses must be 4KB aligned (size=0x{:016X}).",
+ address);
+ return ERR_INVALID_ADDRESS;
+ }
+
+ if (size == 0 || !Common::Is4KBAligned(size)) {
+ LOG_ERROR(Kernel_SVC,
+ "Transfer memory sizes must be 4KB aligned and not be zero (size=0x{:016X}).",
+ size);
+ return ERR_INVALID_SIZE;
+ }
+
+ if (!IsValidAddressRange(address, size)) {
+ LOG_ERROR(Kernel_SVC,
+ "Given address and size overflows the 64-bit range (address=0x{:016X}, "
+ "size=0x{:016X}).",
+ address, size);
+ return ERR_INVALID_ADDRESS_STATE;
+ }
+
+ const auto permissions = static_cast<MemoryPermission>(permission_raw);
+ if (permissions != MemoryPermission::None && permissions != MemoryPermission::Read &&
+ permissions != MemoryPermission::ReadWrite) {
+ LOG_ERROR(Kernel_SVC, "Invalid transfer memory permissions given (permissions=0x{:08X}).",
+ permission_raw);
+ return ERR_INVALID_STATE;
+ }
+
+ const auto& kernel = system.Kernel();
+ const auto* const current_process = kernel.CurrentProcess();
+ const auto& handle_table = current_process->GetHandleTable();
+
+ auto transfer_memory = handle_table.Get<TransferMemory>(handle);
+ if (!transfer_memory) {
+ LOG_ERROR(Kernel_SVC, "Nonexistent transfer memory handle given (handle=0x{:08X}).",
+ handle);
+ return ERR_INVALID_HANDLE;
+ }
+
+ if (!current_process->VMManager().IsWithinASLRRegion(address, size)) {
+ LOG_ERROR(Kernel_SVC,
+ "Given address and size don't fully fit within the ASLR region "
+ "(address=0x{:016X}, size=0x{:016X}).",
+ address, size);
+ return ERR_INVALID_MEMORY_RANGE;
+ }
+
+ return transfer_memory->MapMemory(address, size, permissions);
+}
+
+static ResultCode UnmapTransferMemory(Core::System& system, Handle handle, VAddr address,
+ u64 size) {
+ LOG_DEBUG(Kernel_SVC, "called. handle=0x{:08X}, address=0x{:016X}, size=0x{:016X}", handle,
+ address, size);
+
+ if (!Common::Is4KBAligned(address)) {
+ LOG_ERROR(Kernel_SVC, "Transfer memory addresses must be 4KB aligned (size=0x{:016X}).",
+ address);
+ return ERR_INVALID_ADDRESS;
+ }
+
+ if (size == 0 || !Common::Is4KBAligned(size)) {
+ LOG_ERROR(Kernel_SVC,
+ "Transfer memory sizes must be 4KB aligned and not be zero (size=0x{:016X}).",
+ size);
+ return ERR_INVALID_SIZE;
+ }
+
+ if (!IsValidAddressRange(address, size)) {
+ LOG_ERROR(Kernel_SVC,
+ "Given address and size overflows the 64-bit range (address=0x{:016X}, "
+ "size=0x{:016X}).",
+ address, size);
+ return ERR_INVALID_ADDRESS_STATE;
+ }
+
+ const auto& kernel = system.Kernel();
+ const auto* const current_process = kernel.CurrentProcess();
+ const auto& handle_table = current_process->GetHandleTable();
+
+ auto transfer_memory = handle_table.Get<TransferMemory>(handle);
+ if (!transfer_memory) {
+ LOG_ERROR(Kernel_SVC, "Nonexistent transfer memory handle given (handle=0x{:08X}).",
+ handle);
+ return ERR_INVALID_HANDLE;
+ }
+
+ if (!current_process->VMManager().IsWithinASLRRegion(address, size)) {
+ LOG_ERROR(Kernel_SVC,
+ "Given address and size don't fully fit within the ASLR region "
+ "(address=0x{:016X}, size=0x{:016X}).",
+ address, size);
+ return ERR_INVALID_MEMORY_RANGE;
+ }
+
+ return transfer_memory->UnmapMemory(address, size);
+}
+
+static ResultCode GetThreadCoreMask(Core::System& system, Handle thread_handle, u32* core,
+ u64* mask) {
LOG_TRACE(Kernel_SVC, "called, handle=0x{:08X}", thread_handle);
- const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
+ const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
const SharedPtr<Thread> thread = handle_table.Get<Thread>(thread_handle);
if (!thread) {
LOG_ERROR(Kernel_SVC, "Thread handle does not exist, thread_handle=0x{:08X}",
@@ -1625,57 +1891,65 @@ static ResultCode GetThreadCoreMask(Handle thread_handle, u32* core, u64* mask)
return RESULT_SUCCESS;
}
-static ResultCode SetThreadCoreMask(Handle thread_handle, u32 core, u64 mask) {
- LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, mask=0x{:016X}, core=0x{:X}", thread_handle,
- mask, core);
+static ResultCode SetThreadCoreMask(Core::System& system, Handle thread_handle, u32 core,
+ 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& handle_table = Core::CurrentProcess()->GetHandleTable();
- const SharedPtr<Thread> thread = handle_table.Get<Thread>(thread_handle);
- if (!thread) {
- LOG_ERROR(Kernel_SVC, "Thread handle does not exist, thread_handle=0x{:08X}",
- thread_handle);
- return ERR_INVALID_HANDLE;
- }
+ const auto* const current_process = system.Kernel().CurrentProcess();
if (core == static_cast<u32>(THREADPROCESSORID_IDEAL)) {
- const u8 ideal_cpu_core = thread->GetOwnerProcess()->GetIdealCore();
+ const u8 ideal_cpu_core = current_process->GetIdealCore();
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;
- mask = 1ULL << core;
- }
-
- if (mask == 0) {
- LOG_ERROR(Kernel_SVC, "Mask is 0");
- return ERR_INVALID_COMBINATION;
- }
+ affinity_mask = 1ULL << core;
+ } 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;
+ }
- /// This value is used to only change the affinity mask without changing the current ideal core.
- static constexpr u32 OnlyChangeMask = static_cast<u32>(-3);
+ if (affinity_mask == 0) {
+ LOG_ERROR(Kernel_SVC, "Specfified affinity mask is zero.");
+ return ERR_INVALID_COMBINATION;
+ }
- if (core == OnlyChangeMask) {
- core = thread->GetIdealCore();
- } else if (core >= Core::NUM_CPU_CORES && core != static_cast<u32>(-1)) {
- LOG_ERROR(Kernel_SVC, "Invalid core specified, got {}", core);
- return ERR_INVALID_PROCESSOR_ID;
+ if (core < Core::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;
+ }
+ } 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;
+ }
}
- // Error out if the input core isn't enabled in the input mask.
- if (core < Core::NUM_CPU_CORES && (mask & (1ull << core)) == 0) {
- LOG_ERROR(Kernel_SVC, "Core is not enabled for the current mask, core={}, mask={:016X}",
- core, mask);
- return ERR_INVALID_COMBINATION;
+ const auto& handle_table = current_process->GetHandleTable();
+ const SharedPtr<Thread> thread = handle_table.Get<Thread>(thread_handle);
+ if (!thread) {
+ LOG_ERROR(Kernel_SVC, "Thread handle does not exist, thread_handle=0x{:08X}",
+ thread_handle);
+ return ERR_INVALID_HANDLE;
}
- thread->ChangeCore(core, mask);
-
+ thread->ChangeCore(core, affinity_mask);
return RESULT_SUCCESS;
}
-static ResultCode CreateSharedMemory(Handle* handle, u64 size, u32 local_permissions,
- u32 remote_permissions) {
+static ResultCode CreateSharedMemory(Core::System& system, Handle* handle, u64 size,
+ u32 local_permissions, u32 remote_permissions) {
LOG_TRACE(Kernel_SVC, "called, size=0x{:X}, localPerms=0x{:08X}, remotePerms=0x{:08X}", size,
local_permissions, remote_permissions);
if (size == 0) {
@@ -1711,7 +1985,7 @@ static ResultCode CreateSharedMemory(Handle* handle, u64 size, u32 local_permiss
return ERR_INVALID_MEMORY_PERMISSIONS;
}
- auto& kernel = Core::System::GetInstance().Kernel();
+ auto& kernel = system.Kernel();
auto process = kernel.CurrentProcess();
auto& handle_table = process->GetHandleTable();
auto shared_mem_handle = SharedMemory::Create(kernel, process, size, local_perms, remote_perms);
@@ -1720,12 +1994,12 @@ static ResultCode CreateSharedMemory(Handle* handle, u64 size, u32 local_permiss
return RESULT_SUCCESS;
}
-static ResultCode CreateEvent(Handle* write_handle, Handle* read_handle) {
+static ResultCode CreateEvent(Core::System& system, Handle* write_handle, Handle* read_handle) {
LOG_DEBUG(Kernel_SVC, "called");
- auto& kernel = Core::System::GetInstance().Kernel();
+ auto& kernel = system.Kernel();
const auto [readable_event, writable_event] =
- WritableEvent::CreateEventPair(kernel, ResetType::Sticky, "CreateEvent");
+ WritableEvent::CreateEventPair(kernel, ResetType::Manual, "CreateEvent");
HandleTable& handle_table = kernel.CurrentProcess()->GetHandleTable();
@@ -1748,10 +2022,10 @@ static ResultCode CreateEvent(Handle* write_handle, Handle* read_handle) {
return RESULT_SUCCESS;
}
-static ResultCode ClearEvent(Handle handle) {
+static ResultCode ClearEvent(Core::System& system, Handle handle) {
LOG_TRACE(Kernel_SVC, "called, event=0x{:08X}", handle);
- const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
+ const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
auto writable_event = handle_table.Get<WritableEvent>(handle);
if (writable_event) {
@@ -1769,10 +2043,10 @@ static ResultCode ClearEvent(Handle handle) {
return ERR_INVALID_HANDLE;
}
-static ResultCode SignalEvent(Handle handle) {
+static ResultCode SignalEvent(Core::System& system, Handle handle) {
LOG_DEBUG(Kernel_SVC, "called. Handle=0x{:08X}", handle);
- HandleTable& handle_table = Core::CurrentProcess()->GetHandleTable();
+ HandleTable& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
auto writable_event = handle_table.Get<WritableEvent>(handle);
if (!writable_event) {
@@ -1784,7 +2058,7 @@ static ResultCode SignalEvent(Handle handle) {
return RESULT_SUCCESS;
}
-static ResultCode GetProcessInfo(u64* out, Handle process_handle, u32 type) {
+static ResultCode GetProcessInfo(Core::System& system, u64* out, Handle process_handle, u32 type) {
LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, type=0x{:X}", process_handle, type);
// This function currently only allows retrieving a process' status.
@@ -1792,7 +2066,7 @@ static ResultCode GetProcessInfo(u64* out, Handle process_handle, u32 type) {
Status,
};
- const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
+ const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
const auto process = handle_table.Get<Process>(process_handle);
if (!process) {
LOG_ERROR(Kernel_SVC, "Process handle does not exist, process_handle=0x{:08X}",
@@ -1810,10 +2084,10 @@ static ResultCode GetProcessInfo(u64* out, Handle process_handle, u32 type) {
return RESULT_SUCCESS;
}
-static ResultCode CreateResourceLimit(Handle* out_handle) {
+static ResultCode CreateResourceLimit(Core::System& system, Handle* out_handle) {
LOG_DEBUG(Kernel_SVC, "called");
- auto& kernel = Core::System::GetInstance().Kernel();
+ auto& kernel = system.Kernel();
auto resource_limit = ResourceLimit::Create(kernel);
auto* const current_process = kernel.CurrentProcess();
@@ -1828,11 +2102,11 @@ static ResultCode CreateResourceLimit(Handle* out_handle) {
return RESULT_SUCCESS;
}
-static ResultCode GetResourceLimitLimitValue(u64* out_value, Handle resource_limit,
- u32 resource_type) {
+static ResultCode GetResourceLimitLimitValue(Core::System& system, u64* out_value,
+ Handle resource_limit, u32 resource_type) {
LOG_DEBUG(Kernel_SVC, "called. Handle={:08X}, Resource type={}", resource_limit, resource_type);
- const auto limit_value = RetrieveResourceLimitValue(resource_limit, resource_type,
+ const auto limit_value = RetrieveResourceLimitValue(system, resource_limit, resource_type,
ResourceLimitValueType::LimitValue);
if (limit_value.Failed()) {
return limit_value.Code();
@@ -1842,11 +2116,11 @@ static ResultCode GetResourceLimitLimitValue(u64* out_value, Handle resource_lim
return RESULT_SUCCESS;
}
-static ResultCode GetResourceLimitCurrentValue(u64* out_value, Handle resource_limit,
- u32 resource_type) {
+static ResultCode GetResourceLimitCurrentValue(Core::System& system, u64* out_value,
+ Handle resource_limit, u32 resource_type) {
LOG_DEBUG(Kernel_SVC, "called. Handle={:08X}, Resource type={}", resource_limit, resource_type);
- const auto current_value = RetrieveResourceLimitValue(resource_limit, resource_type,
+ const auto current_value = RetrieveResourceLimitValue(system, resource_limit, resource_type,
ResourceLimitValueType::CurrentValue);
if (current_value.Failed()) {
return current_value.Code();
@@ -1856,7 +2130,8 @@ static ResultCode GetResourceLimitCurrentValue(u64* out_value, Handle resource_l
return RESULT_SUCCESS;
}
-static ResultCode SetResourceLimitLimitValue(Handle resource_limit, u32 resource_type, u64 value) {
+static ResultCode SetResourceLimitLimitValue(Core::System& system, Handle resource_limit,
+ u32 resource_type, u64 value) {
LOG_DEBUG(Kernel_SVC, "called. Handle={:08X}, Resource type={}, Value={}", resource_limit,
resource_type, value);
@@ -1866,8 +2141,7 @@ static ResultCode SetResourceLimitLimitValue(Handle resource_limit, u32 resource
return ERR_INVALID_ENUM_VALUE;
}
- auto& kernel = Core::System::GetInstance().Kernel();
- auto* const current_process = kernel.CurrentProcess();
+ auto* const current_process = system.Kernel().CurrentProcess();
ASSERT(current_process != nullptr);
auto resource_limit_object =
@@ -1891,9 +2165,86 @@ static ResultCode SetResourceLimitLimitValue(Handle resource_limit, u32 resource
return RESULT_SUCCESS;
}
+static ResultCode GetProcessList(Core::System& system, u32* out_num_processes,
+ VAddr out_process_ids, u32 out_process_ids_size) {
+ LOG_DEBUG(Kernel_SVC, "called. out_process_ids=0x{:016X}, out_process_ids_size={}",
+ out_process_ids, out_process_ids_size);
+
+ // If the supplied size is negative or greater than INT32_MAX / sizeof(u64), bail.
+ if ((out_process_ids_size & 0xF0000000) != 0) {
+ LOG_ERROR(Kernel_SVC,
+ "Supplied size outside [0, 0x0FFFFFFF] range. out_process_ids_size={}",
+ out_process_ids_size);
+ return ERR_OUT_OF_RANGE;
+ }
+
+ const auto& kernel = system.Kernel();
+ const auto& vm_manager = kernel.CurrentProcess()->VMManager();
+ const auto total_copy_size = out_process_ids_size * sizeof(u64);
+
+ if (out_process_ids_size > 0 &&
+ !vm_manager.IsWithinAddressSpace(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;
+ }
+
+ const auto& process_list = kernel.GetProcessList();
+ const auto num_processes = process_list.size();
+ const auto copy_amount = std::min(std::size_t{out_process_ids_size}, num_processes);
+
+ for (std::size_t i = 0; i < copy_amount; ++i) {
+ Memory::Write64(out_process_ids, process_list[i]->GetProcessID());
+ out_process_ids += sizeof(u64);
+ }
+
+ *out_num_processes = static_cast<u32>(num_processes);
+ return RESULT_SUCCESS;
+}
+
+static ResultCode GetThreadList(Core::System& system, u32* out_num_threads, VAddr out_thread_ids,
+ u32 out_thread_ids_size, Handle debug_handle) {
+ // TODO: Handle this case when debug events are supported.
+ UNIMPLEMENTED_IF(debug_handle != InvalidHandle);
+
+ LOG_DEBUG(Kernel_SVC, "called. out_thread_ids=0x{:016X}, out_thread_ids_size={}",
+ out_thread_ids, out_thread_ids_size);
+
+ // If the size is negative or larger than INT32_MAX / sizeof(u64)
+ 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;
+ }
+
+ const auto* const current_process = system.Kernel().CurrentProcess();
+ const auto& vm_manager = current_process->VMManager();
+ const auto total_copy_size = out_thread_ids_size * sizeof(u64);
+
+ if (out_thread_ids_size > 0 &&
+ !vm_manager.IsWithinAddressSpace(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;
+ }
+
+ const auto& thread_list = current_process->GetThreadList();
+ const auto num_threads = thread_list.size();
+ const auto copy_amount = std::min(std::size_t{out_thread_ids_size}, num_threads);
+
+ auto list_iter = thread_list.cbegin();
+ for (std::size_t i = 0; i < copy_amount; ++i, ++list_iter) {
+ Memory::Write64(out_thread_ids, (*list_iter)->GetThreadID());
+ out_thread_ids += sizeof(u64);
+ }
+
+ *out_num_threads = static_cast<u32>(num_threads);
+ return RESULT_SUCCESS;
+}
+
namespace {
struct FunctionDef {
- using Func = void();
+ using Func = void(Core::System&);
u32 id;
Func* func;
@@ -1956,7 +2307,7 @@ static const FunctionDef SVC_Table[] = {
{0x33, SvcWrap<GetThreadContext>, "GetThreadContext"},
{0x34, SvcWrap<WaitForAddress>, "WaitForAddress"},
{0x35, SvcWrap<SignalToAddress>, "SignalToAddress"},
- {0x36, nullptr, "Unknown"},
+ {0x36, nullptr, "SynchronizePreemptionState"},
{0x37, nullptr, "Unknown"},
{0x38, nullptr, "Unknown"},
{0x39, nullptr, "Unknown"},
@@ -1983,8 +2334,8 @@ static const FunctionDef SVC_Table[] = {
{0x4E, nullptr, "ReadWriteRegister"},
{0x4F, nullptr, "SetProcessActivity"},
{0x50, SvcWrap<CreateSharedMemory>, "CreateSharedMemory"},
- {0x51, nullptr, "MapTransferMemory"},
- {0x52, nullptr, "UnmapTransferMemory"},
+ {0x51, SvcWrap<MapTransferMemory>, "MapTransferMemory"},
+ {0x52, SvcWrap<UnmapTransferMemory>, "UnmapTransferMemory"},
{0x53, nullptr, "CreateInterruptEvent"},
{0x54, nullptr, "QueryPhysicalAddress"},
{0x55, nullptr, "QueryIoMapping"},
@@ -2003,8 +2354,8 @@ static const FunctionDef SVC_Table[] = {
{0x62, nullptr, "TerminateDebugProcess"},
{0x63, nullptr, "GetDebugEvent"},
{0x64, nullptr, "ContinueDebugEvent"},
- {0x65, nullptr, "GetProcessList"},
- {0x66, nullptr, "GetThreadList"},
+ {0x65, SvcWrap<GetProcessList>, "GetProcessList"},
+ {0x66, SvcWrap<GetThreadList>, "GetThreadList"},
{0x67, nullptr, "GetDebugThreadContext"},
{0x68, nullptr, "SetDebugThreadContext"},
{0x69, nullptr, "QueryDebugProcessMemory"},
@@ -2021,8 +2372,8 @@ static const FunctionDef SVC_Table[] = {
{0x74, nullptr, "MapProcessMemory"},
{0x75, nullptr, "UnmapProcessMemory"},
{0x76, SvcWrap<QueryProcessMemory>, "QueryProcessMemory"},
- {0x77, nullptr, "MapProcessCodeMemory"},
- {0x78, nullptr, "UnmapProcessCodeMemory"},
+ {0x77, SvcWrap<MapProcessCodeMemory>, "MapProcessCodeMemory"},
+ {0x78, SvcWrap<UnmapProcessCodeMemory>, "UnmapProcessCodeMemory"},
{0x79, nullptr, "CreateProcess"},
{0x7A, nullptr, "StartProcess"},
{0x7B, nullptr, "TerminateProcess"},
@@ -2042,16 +2393,16 @@ static const FunctionDef* GetSVCInfo(u32 func_num) {
MICROPROFILE_DEFINE(Kernel_SVC, "Kernel", "SVC", MP_RGB(70, 200, 70));
-void CallSVC(u32 immediate) {
+void CallSVC(Core::System& system, u32 immediate) {
MICROPROFILE_SCOPE(Kernel_SVC);
// Lock the global kernel mutex when we enter the kernel HLE.
- std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
+ std::lock_guard lock{HLE::g_hle_lock};
const FunctionDef* info = GetSVCInfo(immediate);
if (info) {
if (info->func) {
- info->func();
+ info->func(system);
} else {
LOG_CRITICAL(Kernel_SVC, "Unimplemented SVC function {}(..)", info->name);
}
diff --git a/src/core/hle/kernel/svc.h b/src/core/hle/kernel/svc.h
index c37ae0f98..c5539ac1c 100644
--- a/src/core/hle/kernel/svc.h
+++ b/src/core/hle/kernel/svc.h
@@ -6,8 +6,12 @@
#include "common/common_types.h"
+namespace Core {
+class System;
+}
+
namespace Kernel {
-void CallSVC(u32 immediate);
+void CallSVC(Core::System& system, u32 immediate);
} // namespace Kernel
diff --git a/src/core/hle/kernel/svc_wrap.h b/src/core/hle/kernel/svc_wrap.h
index 2a2c2c5ea..865473c6f 100644
--- a/src/core/hle/kernel/svc_wrap.h
+++ b/src/core/hle/kernel/svc_wrap.h
@@ -11,270 +11,319 @@
namespace Kernel {
-static inline u64 Param(int n) {
- return Core::CurrentArmInterface().GetReg(n);
+static inline u64 Param(const Core::System& system, int n) {
+ return system.CurrentArmInterface().GetReg(n);
}
/**
* HLE a function return from the current ARM userland process
- * @param res Result to return
+ * @param system System context
+ * @param result Result to return
*/
-static inline void FuncReturn(u64 res) {
- Core::CurrentArmInterface().SetReg(0, res);
+static inline void FuncReturn(Core::System& system, u64 result) {
+ system.CurrentArmInterface().SetReg(0, result);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// Function wrappers that return type ResultCode
-template <ResultCode func(u64)>
-void SvcWrap() {
- FuncReturn(func(Param(0)).raw);
+template <ResultCode func(Core::System&, u64)>
+void SvcWrap(Core::System& system) {
+ FuncReturn(system, func(system, Param(system, 0)).raw);
}
-template <ResultCode func(u32)>
-void SvcWrap() {
- FuncReturn(func(static_cast<u32>(Param(0))).raw);
+template <ResultCode func(Core::System&, u32)>
+void SvcWrap(Core::System& system) {
+ FuncReturn(system, func(system, static_cast<u32>(Param(system, 0))).raw);
}
-template <ResultCode func(u32, u32)>
-void SvcWrap() {
- FuncReturn(func(static_cast<u32>(Param(0)), static_cast<u32>(Param(1))).raw);
+template <ResultCode func(Core::System&, u32, u32)>
+void SvcWrap(Core::System& system) {
+ FuncReturn(
+ system,
+ func(system, static_cast<u32>(Param(system, 0)), static_cast<u32>(Param(system, 1))).raw);
+}
+
+template <ResultCode func(Core::System&, u32, u64, u64, u64)>
+void SvcWrap(Core::System& system) {
+ FuncReturn(system, func(system, static_cast<u32>(Param(system, 0)), Param(system, 1),
+ Param(system, 2), Param(system, 3))
+ .raw);
}
-template <ResultCode func(u32*)>
-void SvcWrap() {
+template <ResultCode func(Core::System&, u32*)>
+void SvcWrap(Core::System& system) {
u32 param = 0;
- const u32 retval = func(&param).raw;
- Core::CurrentArmInterface().SetReg(1, param);
- FuncReturn(retval);
+ const u32 retval = func(system, &param).raw;
+ system.CurrentArmInterface().SetReg(1, param);
+ FuncReturn(system, retval);
}
-template <ResultCode func(u32*, u32)>
-void SvcWrap() {
+template <ResultCode func(Core::System&, u32*, u32)>
+void SvcWrap(Core::System& system) {
u32 param_1 = 0;
- u32 retval = func(&param_1, static_cast<u32>(Param(1))).raw;
- Core::CurrentArmInterface().SetReg(1, param_1);
- FuncReturn(retval);
+ const u32 retval = func(system, &param_1, static_cast<u32>(Param(system, 1))).raw;
+ system.CurrentArmInterface().SetReg(1, param_1);
+ FuncReturn(system, retval);
}
-template <ResultCode func(u32*, u32*)>
-void SvcWrap() {
+template <ResultCode func(Core::System&, u32*, u32*)>
+void SvcWrap(Core::System& system) {
u32 param_1 = 0;
u32 param_2 = 0;
- const u32 retval = func(&param_1, &param_2).raw;
+ const u32 retval = func(system, &param_1, &param_2).raw;
- auto& arm_interface = Core::CurrentArmInterface();
+ auto& arm_interface = system.CurrentArmInterface();
arm_interface.SetReg(1, param_1);
arm_interface.SetReg(2, param_2);
- FuncReturn(retval);
+ FuncReturn(system, retval);
}
-template <ResultCode func(u32*, u64)>
-void SvcWrap() {
+template <ResultCode func(Core::System&, u32*, u64)>
+void SvcWrap(Core::System& system) {
u32 param_1 = 0;
- const u32 retval = func(&param_1, Param(1)).raw;
- Core::CurrentArmInterface().SetReg(1, param_1);
- FuncReturn(retval);
+ const u32 retval = func(system, &param_1, Param(system, 1)).raw;
+ system.CurrentArmInterface().SetReg(1, param_1);
+ FuncReturn(system, retval);
}
-template <ResultCode func(u64*, u32)>
-void SvcWrap() {
+template <ResultCode func(Core::System&, u32*, u64, u32)>
+void SvcWrap(Core::System& system) {
+ u32 param_1 = 0;
+ const u32 retval =
+ func(system, &param_1, Param(system, 1), static_cast<u32>(Param(system, 2))).raw;
+
+ system.CurrentArmInterface().SetReg(1, param_1);
+ FuncReturn(system, retval);
+}
+
+template <ResultCode func(Core::System&, u64*, u32)>
+void SvcWrap(Core::System& system) {
u64 param_1 = 0;
- const u32 retval = func(&param_1, static_cast<u32>(Param(1))).raw;
- Core::CurrentArmInterface().SetReg(1, param_1);
- FuncReturn(retval);
+ const u32 retval = func(system, &param_1, static_cast<u32>(Param(system, 1))).raw;
+
+ system.CurrentArmInterface().SetReg(1, param_1);
+ FuncReturn(system, retval);
}
-template <ResultCode func(u64, s32)>
-void SvcWrap() {
- FuncReturn(func(Param(0), static_cast<s32>(Param(1))).raw);
+template <ResultCode func(Core::System&, u64, s32)>
+void SvcWrap(Core::System& system) {
+ FuncReturn(system, func(system, Param(system, 0), static_cast<s32>(Param(system, 1))).raw);
}
-template <ResultCode func(u64, u32)>
-void SvcWrap() {
- FuncReturn(func(Param(0), static_cast<u32>(Param(1))).raw);
+template <ResultCode func(Core::System&, u64, u32)>
+void SvcWrap(Core::System& system) {
+ FuncReturn(system, func(system, Param(system, 0), static_cast<u32>(Param(system, 1))).raw);
}
-template <ResultCode func(u64*, u64)>
-void SvcWrap() {
+template <ResultCode func(Core::System&, u64*, u64)>
+void SvcWrap(Core::System& system) {
u64 param_1 = 0;
- u32 retval = func(&param_1, Param(1)).raw;
- Core::CurrentArmInterface().SetReg(1, param_1);
- FuncReturn(retval);
+ const u32 retval = func(system, &param_1, Param(system, 1)).raw;
+
+ system.CurrentArmInterface().SetReg(1, param_1);
+ FuncReturn(system, retval);
}
-template <ResultCode func(u64*, u32, u32)>
-void SvcWrap() {
+template <ResultCode func(Core::System&, u64*, u32, u32)>
+void SvcWrap(Core::System& system) {
u64 param_1 = 0;
- u32 retval = func(&param_1, static_cast<u32>(Param(1)), static_cast<u32>(Param(2))).raw;
- Core::CurrentArmInterface().SetReg(1, param_1);
- FuncReturn(retval);
+ const u32 retval = func(system, &param_1, static_cast<u32>(Param(system, 1)),
+ static_cast<u32>(Param(system, 2)))
+ .raw;
+
+ system.CurrentArmInterface().SetReg(1, param_1);
+ FuncReturn(system, retval);
}
-template <ResultCode func(u32, u64)>
-void SvcWrap() {
- FuncReturn(func(static_cast<u32>(Param(0)), Param(1)).raw);
+template <ResultCode func(Core::System&, u32, u64)>
+void SvcWrap(Core::System& system) {
+ FuncReturn(system, func(system, static_cast<u32>(Param(system, 0)), Param(system, 1)).raw);
}
-template <ResultCode func(u32, u32, u64)>
-void SvcWrap() {
- FuncReturn(func(static_cast<u32>(Param(0)), static_cast<u32>(Param(1)), Param(2)).raw);
+template <ResultCode func(Core::System&, u32, u32, u64)>
+void SvcWrap(Core::System& system) {
+ FuncReturn(system, func(system, static_cast<u32>(Param(system, 0)),
+ static_cast<u32>(Param(system, 1)), Param(system, 2))
+ .raw);
}
-template <ResultCode func(u32, u32*, u64*)>
-void SvcWrap() {
+template <ResultCode func(Core::System&, u32, u32*, u64*)>
+void SvcWrap(Core::System& system) {
u32 param_1 = 0;
u64 param_2 = 0;
- ResultCode retval = func(static_cast<u32>(Param(2)), &param_1, &param_2);
- Core::CurrentArmInterface().SetReg(1, param_1);
- Core::CurrentArmInterface().SetReg(2, param_2);
- FuncReturn(retval.raw);
-}
+ const ResultCode retval = func(system, static_cast<u32>(Param(system, 2)), &param_1, &param_2);
-template <ResultCode func(u64, u64, u32, u32)>
-void SvcWrap() {
- FuncReturn(
- func(Param(0), Param(1), static_cast<u32>(Param(2)), static_cast<u32>(Param(3))).raw);
+ system.CurrentArmInterface().SetReg(1, param_1);
+ system.CurrentArmInterface().SetReg(2, param_2);
+ FuncReturn(system, retval.raw);
}
-template <ResultCode func(u64, u64, u32, u64)>
-void SvcWrap() {
- FuncReturn(func(Param(0), Param(1), static_cast<u32>(Param(2)), Param(3)).raw);
+template <ResultCode func(Core::System&, u64, u64, u32, u32)>
+void SvcWrap(Core::System& system) {
+ FuncReturn(system, func(system, Param(system, 0), Param(system, 1),
+ static_cast<u32>(Param(system, 2)), static_cast<u32>(Param(system, 3)))
+ .raw);
}
-template <ResultCode func(u32, u64, u32)>
-void SvcWrap() {
- FuncReturn(func(static_cast<u32>(Param(0)), Param(1), static_cast<u32>(Param(2))).raw);
+template <ResultCode func(Core::System&, u64, u64, u32, u64)>
+void SvcWrap(Core::System& system) {
+ FuncReturn(system, func(system, Param(system, 0), Param(system, 1),
+ static_cast<u32>(Param(system, 2)), Param(system, 3))
+ .raw);
}
-template <ResultCode func(u64, u64, u64)>
-void SvcWrap() {
- FuncReturn(func(Param(0), Param(1), Param(2)).raw);
+template <ResultCode func(Core::System&, u32, u64, u32)>
+void SvcWrap(Core::System& system) {
+ FuncReturn(system, func(system, static_cast<u32>(Param(system, 0)), Param(system, 1),
+ static_cast<u32>(Param(system, 2)))
+ .raw);
}
-template <ResultCode func(u64, u64, u32)>
-void SvcWrap() {
- FuncReturn(func(Param(0), Param(1), static_cast<u32>(Param(2))).raw);
+template <ResultCode func(Core::System&, u64, u64, u64)>
+void SvcWrap(Core::System& system) {
+ FuncReturn(system, func(system, Param(system, 0), Param(system, 1), Param(system, 2)).raw);
}
-template <ResultCode func(u32, u64, u64, u32)>
-void SvcWrap() {
+template <ResultCode func(Core::System&, u64, u64, u32)>
+void SvcWrap(Core::System& system) {
FuncReturn(
- func(static_cast<u32>(Param(0)), Param(1), Param(2), static_cast<u32>(Param(3))).raw);
+ system,
+ func(system, Param(system, 0), Param(system, 1), static_cast<u32>(Param(system, 2))).raw);
}
-template <ResultCode func(u32, u64, u64)>
-void SvcWrap() {
- FuncReturn(func(static_cast<u32>(Param(0)), Param(1), Param(2)).raw);
+template <ResultCode func(Core::System&, u32, u64, u64, u32)>
+void SvcWrap(Core::System& system) {
+ FuncReturn(system, func(system, static_cast<u32>(Param(system, 0)), Param(system, 1),
+ Param(system, 2), static_cast<u32>(Param(system, 3)))
+ .raw);
}
-template <ResultCode func(u32*, u64, u64, s64)>
-void SvcWrap() {
+template <ResultCode func(Core::System&, u32, u64, u64)>
+void SvcWrap(Core::System& system) {
+ FuncReturn(
+ 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)>
+void SvcWrap(Core::System& system) {
u32 param_1 = 0;
- ResultCode retval =
- func(&param_1, Param(1), static_cast<u32>(Param(2)), static_cast<s64>(Param(3)));
- Core::CurrentArmInterface().SetReg(1, param_1);
- FuncReturn(retval.raw);
+ const u32 retval = func(system, &param_1, Param(system, 1), static_cast<u32>(Param(system, 2)),
+ static_cast<s64>(Param(system, 3)))
+ .raw;
+
+ system.CurrentArmInterface().SetReg(1, param_1);
+ FuncReturn(system, retval);
}
-template <ResultCode func(u64, u64, u32, s64)>
-void SvcWrap() {
- FuncReturn(
- func(Param(0), Param(1), static_cast<u32>(Param(2)), static_cast<s64>(Param(3))).raw);
+template <ResultCode func(Core::System&, u64, u64, u32, s64)>
+void SvcWrap(Core::System& system) {
+ FuncReturn(system, func(system, Param(system, 0), Param(system, 1),
+ static_cast<u32>(Param(system, 2)), static_cast<s64>(Param(system, 3)))
+ .raw);
}
-template <ResultCode func(u64*, u64, u64, u64)>
-void SvcWrap() {
+template <ResultCode func(Core::System&, u64*, u64, u64, u64)>
+void SvcWrap(Core::System& system) {
u64 param_1 = 0;
- u32 retval = func(&param_1, Param(1), Param(2), Param(3)).raw;
- Core::CurrentArmInterface().SetReg(1, param_1);
- FuncReturn(retval);
+ const u32 retval =
+ func(system, &param_1, Param(system, 1), Param(system, 2), Param(system, 3)).raw;
+
+ system.CurrentArmInterface().SetReg(1, param_1);
+ FuncReturn(system, retval);
}
-template <ResultCode func(u32*, u64, u64, u64, u32, s32)>
-void SvcWrap() {
+template <ResultCode func(Core::System&, u32*, u64, u64, u64, u32, s32)>
+void SvcWrap(Core::System& system) {
u32 param_1 = 0;
- u32 retval = func(&param_1, Param(1), Param(2), Param(3), static_cast<u32>(Param(4)),
- static_cast<s32>(Param(5)))
- .raw;
- Core::CurrentArmInterface().SetReg(1, param_1);
- FuncReturn(retval);
+ const u32 retval = func(system, &param_1, Param(system, 1), Param(system, 2), Param(system, 3),
+ static_cast<u32>(Param(system, 4)), static_cast<s32>(Param(system, 5)))
+ .raw;
+
+ system.CurrentArmInterface().SetReg(1, param_1);
+ FuncReturn(system, retval);
}
-template <ResultCode func(u32*, u64, u64, u32)>
-void SvcWrap() {
+template <ResultCode func(Core::System&, u32*, u64, u64, u32)>
+void SvcWrap(Core::System& system) {
u32 param_1 = 0;
- u32 retval = func(&param_1, Param(1), Param(2), static_cast<u32>(Param(3))).raw;
- Core::CurrentArmInterface().SetReg(1, param_1);
- FuncReturn(retval);
+ const u32 retval = func(system, &param_1, Param(system, 1), Param(system, 2),
+ static_cast<u32>(Param(system, 3)))
+ .raw;
+
+ system.CurrentArmInterface().SetReg(1, param_1);
+ FuncReturn(system, retval);
}
-template <ResultCode func(Handle*, u64, u32, u32)>
-void SvcWrap() {
+template <ResultCode func(Core::System&, Handle*, u64, u32, u32)>
+void SvcWrap(Core::System& system) {
u32 param_1 = 0;
- u32 retval =
- func(&param_1, Param(1), static_cast<u32>(Param(2)), static_cast<u32>(Param(3))).raw;
- Core::CurrentArmInterface().SetReg(1, param_1);
- FuncReturn(retval);
+ const u32 retval = func(system, &param_1, Param(system, 1), static_cast<u32>(Param(system, 2)),
+ static_cast<u32>(Param(system, 3)))
+ .raw;
+
+ system.CurrentArmInterface().SetReg(1, param_1);
+ FuncReturn(system, retval);
}
-template <ResultCode func(u64, u32, s32, s64)>
-void SvcWrap() {
- FuncReturn(func(Param(0), static_cast<u32>(Param(1)), static_cast<s32>(Param(2)),
- static_cast<s64>(Param(3)))
- .raw);
+template <ResultCode func(Core::System&, u64, u32, s32, s64)>
+void SvcWrap(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);
}
-template <ResultCode func(u64, u32, s32, s32)>
-void SvcWrap() {
- FuncReturn(func(Param(0), static_cast<u32>(Param(1)), static_cast<s32>(Param(2)),
- static_cast<s32>(Param(3)))
- .raw);
+template <ResultCode func(Core::System&, u64, u32, s32, s32)>
+void SvcWrap(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);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// Function wrappers that return type u32
-template <u32 func()>
-void SvcWrap() {
- FuncReturn(func());
+template <u32 func(Core::System&)>
+void SvcWrap(Core::System& system) {
+ FuncReturn(system, func(system));
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// Function wrappers that return type u64
-template <u64 func()>
-void SvcWrap() {
- FuncReturn(func());
+template <u64 func(Core::System&)>
+void SvcWrap(Core::System& system) {
+ FuncReturn(system, func(system));
}
////////////////////////////////////////////////////////////////////////////////////////////////////
/// Function wrappers that return type void
-template <void func()>
-void SvcWrap() {
- func();
+template <void func(Core::System&)>
+void SvcWrap(Core::System& system) {
+ func(system);
}
-template <void func(s64)>
-void SvcWrap() {
- func(static_cast<s64>(Param(0)));
+template <void func(Core::System&, s64)>
+void SvcWrap(Core::System& system) {
+ func(system, static_cast<s64>(Param(system, 0)));
}
-template <void func(u64, u64 len)>
-void SvcWrap() {
- func(Param(0), Param(1));
+template <void func(Core::System&, u64, u64)>
+void SvcWrap(Core::System& system) {
+ func(system, Param(system, 0), Param(system, 1));
}
-template <void func(u64, u64, u64)>
-void SvcWrap() {
- func(Param(0), Param(1), Param(2));
+template <void func(Core::System&, u64, u64, u64)>
+void SvcWrap(Core::System& system) {
+ func(system, Param(system, 0), Param(system, 1), Param(system, 2));
}
-template <void func(u32, u64, u64)>
-void SvcWrap() {
- func(static_cast<u32>(Param(0)), Param(1), Param(2));
+template <void func(Core::System&, u32, u64, u64)>
+void SvcWrap(Core::System& system) {
+ func(system, static_cast<u32>(Param(system, 0)), Param(system, 1), Param(system, 2));
}
} // namespace Kernel
diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp
index eb54d6651..c73a40977 100644
--- a/src/core/hle/kernel/thread.cpp
+++ b/src/core/hle/kernel/thread.cpp
@@ -7,8 +7,6 @@
#include <optional>
#include <vector>
-#include <boost/range/algorithm_ext/erase.hpp>
-
#include "common/assert.h"
#include "common/common_types.h"
#include "common/logging/log.h"
@@ -30,7 +28,7 @@
namespace Kernel {
-bool Thread::ShouldWait(Thread* thread) const {
+bool Thread::ShouldWait(const Thread* thread) const {
return status != ThreadStatus::Dead;
}
@@ -64,21 +62,12 @@ void Thread::Stop() {
}
wait_objects.clear();
+ owner_process->UnregisterThread(this);
+
// Mark the TLS slot in the thread's page as free.
owner_process->FreeTLSSlot(tls_address);
}
-void WaitCurrentThread_Sleep() {
- Thread* thread = GetCurrentThread();
- thread->SetStatus(ThreadStatus::WaitSleep);
-}
-
-void ExitCurrentThread() {
- Thread* thread = GetCurrentThread();
- thread->Stop();
- Core::System::GetInstance().CurrentScheduler().RemoveThread(thread);
-}
-
void Thread::WakeAfterDelay(s64 nanoseconds) {
// Don't schedule a wakeup if the thread wants to wait forever
if (nanoseconds == -1)
@@ -86,9 +75,9 @@ void Thread::WakeAfterDelay(s64 nanoseconds) {
// This function might be called from any thread so we have to be cautious and use the
// thread-safe version of ScheduleEvent.
+ const s64 cycles = Core::Timing::nsToCycles(std::chrono::nanoseconds{nanoseconds});
Core::System::GetInstance().CoreTiming().ScheduleEventThreadsafe(
- Core::Timing::nsToCycles(nanoseconds), kernel.ThreadWakeupCallbackEventType(),
- callback_handle);
+ cycles, kernel.ThreadWakeupCallbackEventType(), callback_handle);
}
void Thread::CancelWakeupTimer() {
@@ -112,12 +101,12 @@ void Thread::ResumeFromWait() {
ASSERT_MSG(wait_objects.empty(), "Thread is waking up while waiting for objects");
switch (status) {
- case ThreadStatus::WaitSynchAll:
- case ThreadStatus::WaitSynchAny:
+ case ThreadStatus::WaitSynch:
case ThreadStatus::WaitHLEEvent:
case ThreadStatus::WaitSleep:
case ThreadStatus::WaitIPC:
case ThreadStatus::WaitMutex:
+ case ThreadStatus::WaitCondVar:
case ThreadStatus::WaitArb:
break;
@@ -152,6 +141,12 @@ void Thread::ResumeFromWait() {
ChangeScheduler();
}
+void Thread::CancelWait() {
+ ASSERT(GetStatus() == ThreadStatus::WaitSynch);
+ SetWaitSynchronizationResult(ERR_SYNCHRONIZATION_CANCELED);
+ ResumeFromWait();
+}
+
/**
* Resets a thread context, making it ready to be scheduled and run by the CPU
* @param context Thread context to reset
@@ -211,9 +206,11 @@ ResultVal<SharedPtr<Thread>> Thread::Create(KernelCore& kernel, std::string name
thread->callback_handle = kernel.ThreadWakeupCallbackHandleTable().Create(thread).Unwrap();
thread->owner_process = &owner_process;
thread->scheduler = &system.Scheduler(processor_id);
- thread->scheduler->AddThread(thread, priority);
+ thread->scheduler->AddThread(thread);
thread->tls_address = thread->owner_process->MarkNextAvailableTLSSlotAsUsed(*thread);
+ thread->owner_process->RegisterThread(thread.get());
+
// TODO(peachum): move to ScheduleThread() when scheduler is added so selected core is used
// to initialize the context
ResetThreadContext(thread->context, stack_top, entry_point, arg);
@@ -228,11 +225,6 @@ void Thread::SetPriority(u32 priority) {
UpdatePriority();
}
-void Thread::BoostPriority(u32 priority) {
- scheduler->SetThreadPriority(this, priority);
- current_priority = priority;
-}
-
void Thread::SetWaitSynchronizationResult(ResultCode result) {
context.cpu_registers[0] = result.raw;
}
@@ -241,16 +233,16 @@ void Thread::SetWaitSynchronizationOutput(s32 output) {
context.cpu_registers[1] = output;
}
-s32 Thread::GetWaitObjectIndex(WaitObject* object) const {
+s32 Thread::GetWaitObjectIndex(const WaitObject* object) const {
ASSERT_MSG(!wait_objects.empty(), "Thread is not waiting for anything");
- auto match = std::find(wait_objects.rbegin(), wait_objects.rend(), object);
+ 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.
- static constexpr int CommandHeaderOffset = 0x80;
- return GetTLSAddress() + CommandHeaderOffset;
+ constexpr u64 command_header_offset = 0x80;
+ return GetTLSAddress() + command_header_offset;
}
void Thread::SetStatus(ThreadStatus new_status) {
@@ -269,8 +261,8 @@ void Thread::AddMutexWaiter(SharedPtr<Thread> thread) {
if (thread->lock_owner == 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.
- auto itr = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread);
- ASSERT(itr != wait_mutex_threads.end());
+ const auto iter = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread);
+ ASSERT(iter != wait_mutex_threads.end());
return;
}
@@ -278,11 +270,16 @@ void Thread::AddMutexWaiter(SharedPtr<Thread> thread) {
ASSERT(thread->lock_owner == nullptr);
// Ensure that the thread is not already in the list of mutex waiters
- auto itr = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread);
- ASSERT(itr == wait_mutex_threads.end());
-
+ 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 = this;
- wait_mutex_threads.emplace_back(std::move(thread));
+
UpdatePriority();
}
@@ -290,32 +287,44 @@ void Thread::RemoveMutexWaiter(SharedPtr<Thread> thread) {
ASSERT(thread->lock_owner == this);
// Ensure that the thread is in the list of mutex waiters
- auto itr = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread);
- ASSERT(itr != wait_mutex_threads.end());
+ 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);
- boost::remove_erase(wait_mutex_threads, thread);
thread->lock_owner = nullptr;
UpdatePriority();
}
void Thread::UpdatePriority() {
- // Find the highest priority among all the threads that are waiting for this thread's lock
+ // 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;
- for (const auto& thread : wait_mutex_threads) {
- if (thread->nominal_priority < new_priority)
- new_priority = thread->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)
+ if (new_priority == current_priority) {
return;
+ }
scheduler->SetThreadPriority(this, new_priority);
-
current_priority = new_priority;
+ 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(this);
+ old_owner->AddMutexWaiter(this);
+
// Recursively update the priority of the thread that depends on the priority of this one.
- if (lock_owner)
- lock_owner->UpdatePriority();
+ lock_owner->UpdatePriority();
}
void Thread::ChangeCore(u32 core, u64 mask) {
@@ -347,7 +356,7 @@ void Thread::ChangeScheduler() {
if (*new_processor_id != processor_id) {
// Remove thread from previous core's scheduler
scheduler->RemoveThread(this);
- next_scheduler.AddThread(this, current_priority);
+ next_scheduler.AddThread(this);
}
processor_id = *new_processor_id;
@@ -362,7 +371,7 @@ void Thread::ChangeScheduler() {
system.CpuCore(processor_id).PrepareReschedule();
}
-bool Thread::AllWaitObjectsReady() {
+bool Thread::AllWaitObjectsReady() const {
return std::none_of(
wait_objects.begin(), wait_objects.end(),
[this](const SharedPtr<WaitObject>& object) { return object->ShouldWait(this); });
@@ -391,6 +400,14 @@ void Thread::SetActivity(ThreadActivity value) {
}
}
+void Thread::Sleep(s64 nanoseconds) {
+ // Sleep current thread and check for next thread to schedule
+ SetStatus(ThreadStatus::WaitSleep);
+
+ // Create an event to wake the thread up after the specified nanosecond delay has passed
+ WakeAfterDelay(nanoseconds);
+}
+
////////////////////////////////////////////////////////////////////////////////////////////////////
/**
diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h
index c48b21aba..b4b9cda7c 100644
--- a/src/core/hle/kernel/thread.h
+++ b/src/core/hle/kernel/thread.h
@@ -30,12 +30,21 @@ enum ThreadPriority : u32 {
};
enum ThreadProcessorId : s32 {
- THREADPROCESSORID_IDEAL = -2, ///< Run thread on the ideal core specified by the process.
- 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
+ /// 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) |
@@ -49,9 +58,9 @@ enum class ThreadStatus {
WaitHLEEvent, ///< Waiting for hle event to finish
WaitSleep, ///< Waiting due to a SleepThread SVC
WaitIPC, ///< Waiting for the reply from an IPC request
- WaitSynchAny, ///< Waiting due to WaitSynch1 or WaitSynchN with wait_all = false
- WaitSynchAll, ///< Waiting due to WaitSynchronizationN with wait_all = true
- WaitMutex, ///< Waiting due to an ArbitrateLock/WaitProcessWideKey svc
+ 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
@@ -101,16 +110,21 @@ public:
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 const HandleType HANDLE_TYPE = HandleType::Thread;
+ static constexpr HandleType HANDLE_TYPE = HandleType::Thread;
HandleType GetHandleType() const override {
return HANDLE_TYPE;
}
- bool ShouldWait(Thread* thread) const override;
+ bool ShouldWait(const Thread* thread) const override;
void Acquire(Thread* thread) override;
/**
@@ -135,12 +149,6 @@ public:
*/
void SetPriority(u32 priority);
- /**
- * Temporarily boosts the thread's priority until the next time it is scheduled
- * @param priority The new priority
- */
- void BoostPriority(u32 priority);
-
/// Adds a thread to the list of threads that are waiting for a lock held by this thread.
void AddMutexWaiter(SharedPtr<Thread> thread);
@@ -169,11 +177,17 @@ public:
return tls_memory;
}
- /**
- * Resumes a thread from waiting
- */
+ /// Resumes a thread from waiting
void ResumeFromWait();
+ /// 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();
+
/**
* Schedules an event to wake up the specified thread after the specified delay
* @param nanoseconds The time this thread will be allowed to sleep for
@@ -184,27 +198,30 @@ public:
void CancelWakeupTimer();
/**
- * Sets the result after the thread awakens (from either WaitSynchronization SVC)
+ * Sets the result after the thread awakens (from svcWaitSynchronization)
* @param result Value to set to the returned result
*/
void SetWaitSynchronizationResult(ResultCode result);
/**
- * Sets the output parameter value after the thread awakens (from WaitSynchronizationN SVC only)
+ * Sets the output parameter value after the thread awakens (from svcWaitSynchronization)
* @param output Value to set to the output parameter
*/
void SetWaitSynchronizationOutput(s32 output);
/**
* Retrieves the index that this particular object occupies in the list of objects
- * that the thread passed to WaitSynchronizationN, starting the search from the last element.
- * It is used to set the output value of WaitSynchronizationN when the thread is awakened.
+ * 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 GetWaitObjectIndex(WaitObject* object) const;
+ s32 GetWaitObjectIndex(const WaitObject* object) const;
/**
* Stops a thread, invalidating it from further use
@@ -238,13 +255,9 @@ public:
*/
VAddr GetCommandBufferAddress() const;
- /**
- * Returns whether this thread is waiting for all the objects in
- * its wait list to become ready, as a result of a WaitSynchronizationN call
- * with wait_all = true.
- */
- bool IsSleepingOnWaitAll() const {
- return status == ThreadStatus::WaitSynchAll;
+ /// Returns whether this thread is waiting on objects from a WaitSynchronization call.
+ bool IsSleepingOnWait() const {
+ return status == ThreadStatus::WaitSynch;
}
ThreadContext& GetContext() {
@@ -298,7 +311,7 @@ public:
}
/// Determines whether all the objects this thread is waiting on are ready.
- bool AllWaitObjectsReady();
+ bool AllWaitObjectsReady() const;
const MutexWaitingThreads& GetMutexWaitingThreads() const {
return wait_mutex_threads;
@@ -344,10 +357,6 @@ public:
arb_wait_address = address;
}
- void SetGuestHandle(Handle handle) {
- guest_handle = handle;
- }
-
bool HasWakeupCallback() const {
return wakeup_callback != nullptr;
}
@@ -383,6 +392,9 @@ public:
void SetActivity(ThreadActivity value);
+ /// Sleeps this thread for the given amount of nanoseconds.
+ void Sleep(s64 nanoseconds);
+
private:
explicit Thread(KernelCore& kernel);
~Thread() override;
@@ -398,8 +410,14 @@ private:
VAddr entry_point = 0;
VAddr stack_top = 0;
- u32 nominal_priority = 0; ///< Nominal thread priority, as set by the emulated application
- u32 current_priority = 0; ///< Current thread priority, can be temporarily changed
+ /// 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
@@ -413,7 +431,7 @@ private:
Process* owner_process;
/// Objects that the thread is waiting on, in the same order as they were
- /// passed to WaitSynchronization1/N.
+ /// passed to WaitSynchronization.
ThreadWaitObjects wait_objects;
/// List of threads that are waiting for a mutex that is held by this thread.
@@ -432,14 +450,11 @@ private:
/// If waiting for an AddressArbiter, this is the address being waited on.
VAddr arb_wait_address{0};
- /// Handle used by guest emulated application to access this thread
- Handle guest_handle = 0;
-
/// Handle used as userdata to reference this object when inserting into the CoreTiming queue.
Handle callback_handle = 0;
/// Callback that will be invoked when the thread is resumed from a waiting state. If the thread
- /// was waiting via WaitSynchronizationN then the object will be the last object that became
+ /// was waiting via WaitSynchronization then the object will be the last object that became
/// available. In case of a timeout, the object will be nullptr.
WakeupCallback wakeup_callback;
@@ -460,14 +475,4 @@ private:
*/
Thread* GetCurrentThread();
-/**
- * Waits the current thread on a sleep
- */
-void WaitCurrentThread_Sleep();
-
-/**
- * Stops the current thread and removes it from the thread_list
- */
-void ExitCurrentThread();
-
} // namespace Kernel
diff --git a/src/core/hle/kernel/transfer_memory.cpp b/src/core/hle/kernel/transfer_memory.cpp
new file mode 100644
index 000000000..26c4e5e67
--- /dev/null
+++ b/src/core/hle/kernel/transfer_memory.cpp
@@ -0,0 +1,81 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/kernel/errors.h"
+#include "core/hle/kernel/kernel.h"
+#include "core/hle/kernel/process.h"
+#include "core/hle/kernel/shared_memory.h"
+#include "core/hle/kernel/transfer_memory.h"
+#include "core/hle/result.h"
+
+namespace Kernel {
+
+TransferMemory::TransferMemory(KernelCore& kernel) : Object{kernel} {}
+TransferMemory::~TransferMemory() = default;
+
+SharedPtr<TransferMemory> TransferMemory::Create(KernelCore& kernel, VAddr base_address, u64 size,
+ MemoryPermission permissions) {
+ SharedPtr<TransferMemory> transfer_memory{new TransferMemory(kernel)};
+
+ transfer_memory->base_address = base_address;
+ transfer_memory->memory_size = size;
+ transfer_memory->owner_permissions = permissions;
+ transfer_memory->owner_process = kernel.CurrentProcess();
+
+ return transfer_memory;
+}
+
+const u8* TransferMemory::GetPointer() const {
+ return backing_block.get()->data();
+}
+
+u64 TransferMemory::GetSize() const {
+ return memory_size;
+}
+
+ResultCode TransferMemory::MapMemory(VAddr address, u64 size, MemoryPermission permissions) {
+ if (memory_size != size) {
+ return ERR_INVALID_SIZE;
+ }
+
+ if (owner_permissions != permissions) {
+ return ERR_INVALID_STATE;
+ }
+
+ if (is_mapped) {
+ return ERR_INVALID_STATE;
+ }
+
+ backing_block = std::make_shared<std::vector<u8>>(size);
+
+ const auto map_state = owner_permissions == MemoryPermission::None
+ ? MemoryState::TransferMemoryIsolated
+ : MemoryState::TransferMemory;
+ auto& vm_manager = owner_process->VMManager();
+ const auto map_result = vm_manager.MapMemoryBlock(address, backing_block, 0, size, map_state);
+ if (map_result.Failed()) {
+ return map_result.Code();
+ }
+
+ is_mapped = true;
+ return RESULT_SUCCESS;
+}
+
+ResultCode TransferMemory::UnmapMemory(VAddr address, u64 size) {
+ if (memory_size != size) {
+ return ERR_INVALID_SIZE;
+ }
+
+ auto& vm_manager = owner_process->VMManager();
+ const auto result = vm_manager.UnmapRange(address, size);
+
+ if (result.IsError()) {
+ return result;
+ }
+
+ is_mapped = false;
+ return RESULT_SUCCESS;
+}
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/transfer_memory.h b/src/core/hle/kernel/transfer_memory.h
new file mode 100644
index 000000000..a140b1e2b
--- /dev/null
+++ b/src/core/hle/kernel/transfer_memory.h
@@ -0,0 +1,103 @@
+// Copyright 2019 yuzu emulator team
+// 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"
+
+union ResultCode;
+
+namespace Kernel {
+
+class KernelCore;
+class Process;
+
+enum class MemoryPermission : u32;
+
+/// Defines the interface for transfer memory objects.
+///
+/// Transfer memory is typically used for the purpose of
+/// transferring memory between separate process instances,
+/// thus the name.
+///
+class TransferMemory final : public Object {
+public:
+ static constexpr HandleType HANDLE_TYPE = HandleType::TransferMemory;
+
+ static SharedPtr<TransferMemory> Create(KernelCore& kernel, VAddr base_address, u64 size,
+ MemoryPermission permissions);
+
+ TransferMemory(const TransferMemory&) = delete;
+ TransferMemory& operator=(const TransferMemory&) = delete;
+
+ TransferMemory(TransferMemory&&) = delete;
+ TransferMemory& operator=(TransferMemory&&) = delete;
+
+ std::string GetTypeName() const override {
+ return "TransferMemory";
+ }
+
+ std::string GetName() const override {
+ return GetTypeName();
+ }
+
+ HandleType GetHandleType() const override {
+ return HANDLE_TYPE;
+ }
+
+ /// Gets a pointer to the backing block of this instance.
+ const u8* GetPointer() const;
+
+ /// Gets the size of the memory backing this instance in bytes.
+ u64 GetSize() const;
+
+ /// Attempts to map transfer memory with the given range and memory permissions.
+ ///
+ /// @param address The base address to being mapping memory at.
+ /// @param size The size of the memory to map, in bytes.
+ /// @param permissions The memory permissions to check against when mapping memory.
+ ///
+ /// @pre The given address, size, and memory permissions must all match
+ /// the same values that were given when creating the transfer memory
+ /// instance.
+ ///
+ ResultCode MapMemory(VAddr address, u64 size, MemoryPermission permissions);
+
+ /// Unmaps the transfer memory with the given range
+ ///
+ /// @param address The base address to begin unmapping memory at.
+ /// @param size The size of the memory to unmap, in bytes.
+ ///
+ /// @pre The given address and size must be the same as the ones used
+ /// to create the transfer memory instance.
+ ///
+ ResultCode UnmapMemory(VAddr address, u64 size);
+
+private:
+ explicit TransferMemory(KernelCore& kernel);
+ ~TransferMemory() override;
+
+ /// Memory block backing this instance.
+ std::shared_ptr<std::vector<u8>> backing_block;
+
+ /// The base address for the memory managed by this instance.
+ VAddr base_address = 0;
+
+ /// Size of the memory, in bytes, that this instance manages.
+ u64 memory_size = 0;
+
+ /// The memory permissions that are applied to this instance.
+ MemoryPermission owner_permissions{};
+
+ /// The process that this transfer memory instance was created under.
+ Process* owner_process = nullptr;
+
+ /// Whether or not this transfer memory instance has mapped memory.
+ bool is_mapped = false;
+};
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/vm_manager.cpp b/src/core/hle/kernel/vm_manager.cpp
index 05c59af34..c929c2a52 100644
--- a/src/core/hle/kernel/vm_manager.cpp
+++ b/src/core/hle/kernel/vm_manager.cpp
@@ -7,29 +7,29 @@
#include <utility>
#include "common/assert.h"
#include "common/logging/log.h"
+#include "common/memory_hook.h"
#include "core/arm/arm_interface.h"
#include "core/core.h"
#include "core/file_sys/program_metadata.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/vm_manager.h"
#include "core/memory.h"
-#include "core/memory_hook.h"
#include "core/memory_setup.h"
namespace Kernel {
namespace {
const char* GetMemoryStateName(MemoryState state) {
static constexpr const char* names[] = {
- "Unmapped", "Io",
- "Normal", "CodeStatic",
- "CodeMutable", "Heap",
- "Shared", "Unknown1",
- "ModuleCodeStatic", "ModuleCodeMutable",
- "IpcBuffer0", "Stack",
- "ThreadLocal", "TransferMemoryIsolated",
- "TransferMemory", "ProcessMemory",
- "Inaccessible", "IpcBuffer1",
- "IpcBuffer3", "KernelStack",
+ "Unmapped", "Io",
+ "Normal", "Code",
+ "CodeData", "Heap",
+ "Shared", "Unknown1",
+ "ModuleCode", "ModuleCodeData",
+ "IpcBuffer0", "Stack",
+ "ThreadLocal", "TransferMemoryIsolated",
+ "TransferMemory", "ProcessMemory",
+ "Inaccessible", "IpcBuffer1",
+ "IpcBuffer3", "KernelStack",
};
return names[ToSvcMemoryState(state)];
@@ -62,15 +62,13 @@ bool VirtualMemoryArea::CanBeMergedWith(const VirtualMemoryArea& next) const {
return true;
}
-VMManager::VMManager() {
+VMManager::VMManager(Core::System& system) : system{system} {
// Default to assuming a 39-bit address space. This way we have a sane
// starting point with executables that don't provide metadata.
Reset(FileSys::ProgramAddressSpaceType::Is39Bit);
}
-VMManager::~VMManager() {
- Reset(FileSys::ProgramAddressSpaceType::Is39Bit);
-}
+VMManager::~VMManager() = default;
void VMManager::Reset(FileSys::ProgramAddressSpaceType type) {
Clear();
@@ -111,7 +109,6 @@ ResultVal<VMManager::VMAHandle> VMManager::MapMemoryBlock(VAddr target,
VirtualMemoryArea& final_vma = vma_handle->second;
ASSERT(final_vma.size == size);
- auto& system = Core::System::GetInstance();
system.ArmInterface(0).MapBackingMemory(target, size, block->data() + offset,
VMAPermission::ReadWriteExecute);
system.ArmInterface(1).MapBackingMemory(target, size, block->data() + offset,
@@ -140,7 +137,6 @@ ResultVal<VMManager::VMAHandle> VMManager::MapBackingMemory(VAddr target, u8* me
VirtualMemoryArea& final_vma = vma_handle->second;
ASSERT(final_vma.size == size);
- auto& system = Core::System::GetInstance();
system.ArmInterface(0).MapBackingMemory(target, size, memory, VMAPermission::ReadWriteExecute);
system.ArmInterface(1).MapBackingMemory(target, size, memory, VMAPermission::ReadWriteExecute);
system.ArmInterface(2).MapBackingMemory(target, size, memory, VMAPermission::ReadWriteExecute);
@@ -177,7 +173,7 @@ ResultVal<VAddr> VMManager::FindFreeRegion(u64 size) const {
ResultVal<VMManager::VMAHandle> VMManager::MapMMIO(VAddr target, PAddr paddr, u64 size,
MemoryState state,
- Memory::MemoryHookPointer mmio_handler) {
+ Common::MemoryHookPointer mmio_handler) {
// This is the appropriately sized VMA that will turn into our allocation.
CASCADE_RESULT(VMAIter vma_handle, CarveVMA(target, size));
VirtualMemoryArea& final_vma = vma_handle->second;
@@ -223,7 +219,6 @@ ResultCode VMManager::UnmapRange(VAddr target, u64 size) {
ASSERT(FindVMA(target)->second.size >= size);
- auto& system = Core::System::GetInstance();
system.ArmInterface(0).UnmapMemory(target, size);
system.ArmInterface(1).UnmapMemory(target, size);
system.ArmInterface(2).UnmapMemory(target, size);
@@ -256,57 +251,130 @@ ResultCode VMManager::ReprotectRange(VAddr target, u64 size, VMAPermission new_p
return RESULT_SUCCESS;
}
-ResultVal<VAddr> VMManager::HeapAllocate(VAddr target, u64 size, VMAPermission perms) {
- if (!IsWithinHeapRegion(target, size)) {
- return ERR_INVALID_ADDRESS;
+ResultVal<VAddr> VMManager::SetHeapSize(u64 size) {
+ if (size > GetHeapRegionSize()) {
+ return ERR_OUT_OF_MEMORY;
+ }
+
+ // No need to do any additional work if the heap is already the given size.
+ if (size == GetCurrentHeapSize()) {
+ return MakeResult(heap_region_base);
}
if (heap_memory == nullptr) {
// Initialize heap
- heap_memory = std::make_shared<std::vector<u8>>();
- heap_start = heap_end = target;
+ heap_memory = std::make_shared<std::vector<u8>>(size);
+ heap_end = heap_region_base + size;
} else {
- UnmapRange(heap_start, heap_end - heap_start);
+ UnmapRange(heap_region_base, GetCurrentHeapSize());
}
- // If necessary, expand backing vector to cover new heap extents.
- if (target < heap_start) {
- heap_memory->insert(begin(*heap_memory), heap_start - target, 0);
- heap_start = target;
+ // If necessary, expand backing vector to cover new heap extents in
+ // the case of allocating. Otherwise, shrink the backing memory,
+ // if a smaller heap has been requested.
+ const u64 old_heap_size = GetCurrentHeapSize();
+ if (size > old_heap_size) {
+ const u64 alloc_size = size - old_heap_size;
+
+ heap_memory->insert(heap_memory->end(), alloc_size, 0);
RefreshMemoryBlockMappings(heap_memory.get());
- }
- if (target + size > heap_end) {
- heap_memory->insert(end(*heap_memory), (target + size) - heap_end, 0);
- heap_end = target + size;
+ } else if (size < old_heap_size) {
+ heap_memory->resize(size);
+ heap_memory->shrink_to_fit();
+
RefreshMemoryBlockMappings(heap_memory.get());
}
- ASSERT(heap_end - heap_start == heap_memory->size());
- CASCADE_RESULT(auto vma, MapMemoryBlock(target, heap_memory, target - heap_start, size,
- MemoryState::Heap));
- Reprotect(vma, perms);
+ heap_end = heap_region_base + size;
+ ASSERT(GetCurrentHeapSize() == heap_memory->size());
- heap_used = size;
+ const auto mapping_result =
+ MapMemoryBlock(heap_region_base, heap_memory, 0, size, MemoryState::Heap);
+ if (mapping_result.Failed()) {
+ return mapping_result.Code();
+ }
- return MakeResult<VAddr>(heap_end - size);
+ return MakeResult<VAddr>(heap_region_base);
}
-ResultCode VMManager::HeapFree(VAddr target, u64 size) {
- if (!IsWithinHeapRegion(target, size)) {
- return ERR_INVALID_ADDRESS;
+ResultCode VMManager::MapCodeMemory(VAddr dst_address, VAddr src_address, u64 size) {
+ constexpr auto ignore_attribute = MemoryAttribute::LockedForIPC | MemoryAttribute::DeviceMapped;
+ const auto src_check_result = CheckRangeState(
+ src_address, size, MemoryState::All, MemoryState::Heap, VMAPermission::All,
+ VMAPermission::ReadWrite, MemoryAttribute::Mask, MemoryAttribute::None, ignore_attribute);
+
+ if (src_check_result.Failed()) {
+ return src_check_result.Code();
}
- if (size == 0) {
- return RESULT_SUCCESS;
+ const auto mirror_result =
+ MirrorMemory(dst_address, src_address, size, MemoryState::ModuleCode);
+ if (mirror_result.IsError()) {
+ return mirror_result;
}
- const ResultCode result = UnmapRange(target, size);
- if (result.IsError()) {
- return result;
+ // Ensure we lock the source memory region.
+ const auto src_vma_result = CarveVMARange(src_address, size);
+ if (src_vma_result.Failed()) {
+ return src_vma_result.Code();
}
+ auto src_vma_iter = *src_vma_result;
+ src_vma_iter->second.attribute = MemoryAttribute::Locked;
+ Reprotect(src_vma_iter, VMAPermission::Read);
- heap_used -= size;
- return RESULT_SUCCESS;
+ // The destination memory region is fine as is, however we need to make it read-only.
+ return ReprotectRange(dst_address, size, VMAPermission::Read);
+}
+
+ResultCode VMManager::UnmapCodeMemory(VAddr dst_address, VAddr src_address, u64 size) {
+ constexpr auto ignore_attribute = MemoryAttribute::LockedForIPC | MemoryAttribute::DeviceMapped;
+ const auto src_check_result = CheckRangeState(
+ src_address, size, MemoryState::All, MemoryState::Heap, VMAPermission::None,
+ VMAPermission::None, MemoryAttribute::Mask, MemoryAttribute::Locked, ignore_attribute);
+
+ if (src_check_result.Failed()) {
+ return src_check_result.Code();
+ }
+
+ // Yes, the kernel only checks the first page of the region.
+ const auto dst_check_result =
+ CheckRangeState(dst_address, Memory::PAGE_SIZE, MemoryState::FlagModule,
+ MemoryState::FlagModule, VMAPermission::None, VMAPermission::None,
+ MemoryAttribute::Mask, MemoryAttribute::None, ignore_attribute);
+
+ if (dst_check_result.Failed()) {
+ return dst_check_result.Code();
+ }
+
+ const auto dst_memory_state = std::get<MemoryState>(*dst_check_result);
+ const auto dst_contiguous_check_result = CheckRangeState(
+ dst_address, size, MemoryState::All, dst_memory_state, VMAPermission::None,
+ VMAPermission::None, MemoryAttribute::Mask, MemoryAttribute::None, ignore_attribute);
+
+ if (dst_contiguous_check_result.Failed()) {
+ return dst_contiguous_check_result.Code();
+ }
+
+ const auto unmap_result = UnmapRange(dst_address, size);
+ if (unmap_result.IsError()) {
+ return unmap_result;
+ }
+
+ // With the mirrored portion unmapped, restore the original region's traits.
+ const auto src_vma_result = CarveVMARange(src_address, size);
+ if (src_vma_result.Failed()) {
+ return src_vma_result.Code();
+ }
+ auto src_vma_iter = *src_vma_result;
+ src_vma_iter->second.state = MemoryState::Heap;
+ src_vma_iter->second.attribute = MemoryAttribute::None;
+ Reprotect(src_vma_iter, VMAPermission::ReadWrite);
+
+ if (dst_memory_state == MemoryState::ModuleCode) {
+ system.InvalidateCpuInstructionCaches();
+ }
+
+ return unmap_result;
}
MemoryInfo VMManager::QueryMemory(VAddr address) const {
@@ -598,6 +666,7 @@ void VMManager::InitializeMemoryRegionRanges(FileSys::ProgramAddressSpaceType ty
heap_region_base = map_region_end;
heap_region_end = heap_region_base + heap_region_size;
+ heap_end = heap_region_base;
new_map_region_base = heap_region_end;
new_map_region_end = new_map_region_base + new_map_region_size;
@@ -624,7 +693,7 @@ void VMManager::ClearPageTable() {
std::fill(page_table.pointers.begin(), page_table.pointers.end(), nullptr);
page_table.special_regions.clear();
std::fill(page_table.attributes.begin(), page_table.attributes.end(),
- Memory::PageType::Unmapped);
+ Common::PageType::Unmapped);
}
VMManager::CheckResults VMManager::CheckRangeState(VAddr address, u64 size, MemoryState state_mask,
@@ -687,15 +756,11 @@ VMManager::CheckResults VMManager::CheckRangeState(VAddr address, u64 size, Memo
std::make_tuple(initial_state, initial_permissions, initial_attributes & ~ignore_mask));
}
-u64 VMManager::GetTotalMemoryUsage() const {
+u64 VMManager::GetTotalPhysicalMemoryAvailable() const {
LOG_WARNING(Kernel, "(STUBBED) called");
return 0xF8000000;
}
-u64 VMManager::GetTotalHeapUsage() const {
- return heap_used;
-}
-
VAddr VMManager::GetAddressSpaceBaseAddress() const {
return address_space_base;
}
@@ -778,6 +843,10 @@ u64 VMManager::GetHeapRegionSize() const {
return heap_region_end - heap_region_base;
}
+u64 VMManager::GetCurrentHeapSize() const {
+ return heap_end - heap_region_base;
+}
+
bool VMManager::IsWithinHeapRegion(VAddr address, u64 size) const {
return IsInsideAddressRange(address, size, GetHeapRegionBaseAddress(),
GetHeapRegionEndAddress());
diff --git a/src/core/hle/kernel/vm_manager.h b/src/core/hle/kernel/vm_manager.h
index 88e0b3c02..dfbf7a894 100644
--- a/src/core/hle/kernel/vm_manager.h
+++ b/src/core/hle/kernel/vm_manager.h
@@ -9,9 +9,14 @@
#include <tuple>
#include <vector>
#include "common/common_types.h"
+#include "common/memory_hook.h"
+#include "common/page_table.h"
#include "core/hle/result.h"
#include "core/memory.h"
-#include "core/memory_hook.h"
+
+namespace Core {
+class System;
+}
namespace FileSys {
enum class ProgramAddressSpaceType : u8;
@@ -42,6 +47,9 @@ enum class VMAPermission : u8 {
ReadExecute = Read | Execute,
WriteExecute = Write | Execute,
ReadWriteExecute = Read | Write | Execute,
+
+ // Used as a wildcard when checking permissions across memory ranges
+ All = 0xFF,
};
constexpr VMAPermission operator|(VMAPermission lhs, VMAPermission rhs) {
@@ -151,6 +159,9 @@ enum class MemoryState : u32 {
FlagUncached = 1U << 24,
FlagCodeMemory = 1U << 25,
+ // Wildcard used in range checking to indicate all states.
+ All = 0xFFFFFFFF,
+
// Convenience flag sets to reduce repetition
IPCFlags = FlagIPC0 | FlagIPC3 | FlagIPC1,
@@ -164,12 +175,12 @@ enum class MemoryState : u32 {
Unmapped = 0x00,
Io = 0x01 | FlagMapped,
Normal = 0x02 | FlagMapped | FlagQueryPhysicalAddressAllowed,
- CodeStatic = 0x03 | CodeFlags | FlagMapProcess,
- CodeMutable = 0x04 | CodeFlags | FlagMapProcess | FlagCodeMemory,
+ Code = 0x03 | CodeFlags | FlagMapProcess,
+ CodeData = 0x04 | DataFlags | FlagMapProcess | FlagCodeMemory,
Heap = 0x05 | DataFlags | FlagCodeMemory,
Shared = 0x06 | FlagMapped | FlagMemoryPoolAllocated,
- ModuleCodeStatic = 0x08 | CodeFlags | FlagModule | FlagMapProcess,
- ModuleCodeMutable = 0x09 | DataFlags | FlagModule | FlagMapProcess | FlagCodeMemory,
+ ModuleCode = 0x08 | CodeFlags | FlagModule | FlagMapProcess,
+ ModuleCodeData = 0x09 | DataFlags | FlagModule | FlagMapProcess | FlagCodeMemory,
IpcBuffer0 = 0x0A | FlagMapped | FlagQueryPhysicalAddressAllowed | FlagMemoryPoolAllocated |
IPCFlags | FlagSharedDevice | FlagSharedDeviceAligned,
@@ -290,7 +301,7 @@ struct VirtualMemoryArea {
// Settings for type = MMIO
/// Physical address of the register area this VMA maps to.
PAddr paddr = 0;
- Memory::MemoryHookPointer mmio_handler = nullptr;
+ Common::MemoryHookPointer mmio_handler = nullptr;
/// Tests if this area can be merged to the right with `next`.
bool CanBeMergedWith(const VirtualMemoryArea& next) const;
@@ -314,7 +325,7 @@ class VMManager final {
public:
using VMAHandle = VMAMap::const_iterator;
- VMManager();
+ explicit VMManager(Core::System& system);
~VMManager();
/// Clears the address space map, re-initializing with a single free area.
@@ -368,7 +379,7 @@ public:
* @param mmio_handler The handler that will implement read and write for this MMIO region.
*/
ResultVal<VMAHandle> MapMMIO(VAddr target, PAddr paddr, u64 size, MemoryState state,
- Memory::MemoryHookPointer mmio_handler);
+ Common::MemoryHookPointer mmio_handler);
/// Unmaps a range of addresses, splitting VMAs as necessary.
ResultCode UnmapRange(VAddr target, u64 size);
@@ -379,11 +390,84 @@ public:
/// Changes the permissions of a range of addresses, splitting VMAs as necessary.
ResultCode ReprotectRange(VAddr target, u64 size, VMAPermission new_perms);
- ResultVal<VAddr> HeapAllocate(VAddr target, u64 size, VMAPermission perms);
- ResultCode HeapFree(VAddr target, u64 size);
-
ResultCode MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size, MemoryState state);
+ /// Attempts to allocate a heap with the given size.
+ ///
+ /// @param size The size of the heap to allocate in bytes.
+ ///
+ /// @note If a heap is currently allocated, and this is called
+ /// with a size that is equal to the size of the current heap,
+ /// then this function will do nothing and return the current
+ /// heap's starting address, as there's no need to perform
+ /// any additional heap allocation work.
+ ///
+ /// @note If a heap is currently allocated, and this is called
+ /// with a size less than the current heap's size, then
+ /// this function will attempt to shrink the heap.
+ ///
+ /// @note If a heap is currently allocated, and this is called
+ /// with a size larger than the current heap's size, then
+ /// this function will attempt to extend the size of the heap.
+ ///
+ /// @returns A result indicating either success or failure.
+ /// <p>
+ /// If successful, this function will return a result
+ /// containing the starting address to the allocated heap.
+ /// <p>
+ /// If unsuccessful, this function will return a result
+ /// containing an error code.
+ ///
+ /// @pre The given size must lie within the allowable heap
+ /// memory region managed by this VMManager instance.
+ /// Failure to abide by this will result in ERR_OUT_OF_MEMORY
+ /// being returned as the result.
+ ///
+ ResultVal<VAddr> SetHeapSize(u64 size);
+
+ /// Maps a region of memory as code memory.
+ ///
+ /// @param dst_address The base address of the region to create the aliasing memory region.
+ /// @param src_address The base address of the region to be aliased.
+ /// @param size The total amount of memory to map in bytes.
+ ///
+ /// @pre Both memory regions lie within the actual addressable address space.
+ ///
+ /// @post After this function finishes execution, assuming success, then the address range
+ /// [dst_address, dst_address+size) will alias the memory region,
+ /// [src_address, src_address+size).
+ /// <p>
+ /// What this also entails is as follows:
+ /// 1. The aliased region gains the Locked memory attribute.
+ /// 2. The aliased region becomes read-only.
+ /// 3. The aliasing region becomes read-only.
+ /// 4. The aliasing region is created with a memory state of MemoryState::CodeModule.
+ ///
+ ResultCode MapCodeMemory(VAddr dst_address, VAddr src_address, u64 size);
+
+ /// Unmaps a region of memory designated as code module memory.
+ ///
+ /// @param dst_address The base address of the memory region aliasing the source memory region.
+ /// @param src_address The base address of the memory region being aliased.
+ /// @param size The size of the memory region to unmap in bytes.
+ ///
+ /// @pre Both memory ranges lie within the actual addressable address space.
+ ///
+ /// @pre The memory region being unmapped has been previously been mapped
+ /// by a call to MapCodeMemory.
+ ///
+ /// @post After execution of the function, if successful. the aliasing memory region
+ /// will be unmapped and the aliased region will have various traits about it
+ /// restored to what they were prior to the original mapping call preceding
+ /// this function call.
+ /// <p>
+ /// What this also entails is as follows:
+ /// 1. The state of the memory region will now indicate a general heap region.
+ /// 2. All memory attributes for the memory region are cleared.
+ /// 3. Memory permissions for the region are restored to user read/write.
+ ///
+ ResultCode UnmapCodeMemory(VAddr dst_address, VAddr src_address, u64 size);
+
/// Queries the memory manager for information about the given address.
///
/// @param address The address to query the memory manager about for information.
@@ -415,10 +499,7 @@ public:
void LogLayout() const;
/// Gets the total memory usage, used by svcGetInfo
- u64 GetTotalMemoryUsage() const;
-
- /// Gets the total heap usage, used by svcGetInfo
- u64 GetTotalHeapUsage() const;
+ u64 GetTotalPhysicalMemoryAvailable() const;
/// Gets the address space base address
VAddr GetAddressSpaceBaseAddress() const;
@@ -468,6 +549,13 @@ public:
/// Gets the total size of the heap region in bytes.
u64 GetHeapRegionSize() const;
+ /// Gets the total size of the current heap in bytes.
+ ///
+ /// @note This is the current allocated heap size, not the size
+ /// of the region it's allowed to exist within.
+ ///
+ u64 GetCurrentHeapSize() const;
+
/// Determines whether or not the specified range is within the heap region.
bool IsWithinHeapRegion(VAddr address, u64 size) const;
@@ -509,7 +597,7 @@ public:
/// Each VMManager has its own page table, which is set as the main one when the owning process
/// is scheduled.
- Memory::PageTable page_table;
+ Common::PageTable page_table{Memory::PAGE_BITS};
private:
using VMAIter = VMAMap::iterator;
@@ -624,9 +712,11 @@ private:
// This makes deallocation and reallocation of holes fast and keeps process memory contiguous
// in the emulator address space, allowing Memory::GetPointer to be reasonably safe.
std::shared_ptr<std::vector<u8>> heap_memory;
- // The left/right bounds of the address space covered by heap_memory.
- VAddr heap_start = 0;
+
+ // The end of the currently allocated heap. This is not an inclusive
+ // end of the range. This is essentially 'base_address + current_size'.
VAddr heap_end = 0;
- u64 heap_used = 0;
+
+ Core::System& system;
};
} // namespace Kernel
diff --git a/src/core/hle/kernel/wait_object.cpp b/src/core/hle/kernel/wait_object.cpp
index 90580ed93..0e96ba872 100644
--- a/src/core/hle/kernel/wait_object.cpp
+++ b/src/core/hle/kernel/wait_object.cpp
@@ -30,7 +30,7 @@ void WaitObject::RemoveWaitingThread(Thread* thread) {
waiting_threads.erase(itr);
}
-SharedPtr<Thread> WaitObject::GetHighestPriorityReadyThread() {
+SharedPtr<Thread> WaitObject::GetHighestPriorityReadyThread() const {
Thread* candidate = nullptr;
u32 candidate_priority = THREADPRIO_LOWEST + 1;
@@ -38,8 +38,7 @@ SharedPtr<Thread> WaitObject::GetHighestPriorityReadyThread() {
const ThreadStatus thread_status = thread->GetStatus();
// The list of waiting threads must not contain threads that are not waiting to be awakened.
- ASSERT_MSG(thread_status == ThreadStatus::WaitSynchAny ||
- thread_status == ThreadStatus::WaitSynchAll ||
+ ASSERT_MSG(thread_status == ThreadStatus::WaitSynch ||
thread_status == ThreadStatus::WaitHLEEvent,
"Inconsistent thread statuses in waiting_threads");
@@ -49,10 +48,10 @@ SharedPtr<Thread> WaitObject::GetHighestPriorityReadyThread() {
if (ShouldWait(thread.get()))
continue;
- // A thread is ready to run if it's either in ThreadStatus::WaitSynchAny or
- // in ThreadStatus::WaitSynchAll and the rest of the objects it is waiting on are ready.
+ // A thread is ready to run if it's either in ThreadStatus::WaitSynch
+ // and the rest of the objects it is waiting on are ready.
bool ready_to_run = true;
- if (thread_status == ThreadStatus::WaitSynchAll) {
+ if (thread_status == ThreadStatus::WaitSynch) {
ready_to_run = thread->AllWaitObjectsReady();
}
@@ -68,33 +67,35 @@ SharedPtr<Thread> WaitObject::GetHighestPriorityReadyThread() {
void WaitObject::WakeupWaitingThread(SharedPtr<Thread> thread) {
ASSERT(!ShouldWait(thread.get()));
- if (!thread)
+ if (!thread) {
return;
+ }
- if (!thread->IsSleepingOnWaitAll()) {
- Acquire(thread.get());
- } else {
+ if (thread->IsSleepingOnWait()) {
for (const auto& object : thread->GetWaitObjects()) {
ASSERT(!object->ShouldWait(thread.get()));
object->Acquire(thread.get());
}
+ } else {
+ Acquire(thread.get());
}
const std::size_t index = thread->GetWaitObjectIndex(this);
- for (const auto& object : thread->GetWaitObjects())
+ for (const auto& object : thread->GetWaitObjects()) {
object->RemoveWaitingThread(thread.get());
+ }
thread->ClearWaitObjects();
thread->CancelWakeupTimer();
bool resume = true;
-
- if (thread->HasWakeupCallback())
+ if (thread->HasWakeupCallback()) {
resume = thread->InvokeWakeupCallback(ThreadWakeupReason::Signal, thread, this, index);
-
- if (resume)
+ }
+ if (resume) {
thread->ResumeFromWait();
+ }
}
void WaitObject::WakeupAllWaitingThreads() {
diff --git a/src/core/hle/kernel/wait_object.h b/src/core/hle/kernel/wait_object.h
index 5987fb971..3271a30a7 100644
--- a/src/core/hle/kernel/wait_object.h
+++ b/src/core/hle/kernel/wait_object.h
@@ -24,7 +24,7 @@ public:
* @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(Thread* thread) const = 0;
+ 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;
@@ -54,7 +54,7 @@ public:
void WakeupWaitingThread(SharedPtr<Thread> thread);
/// Obtains the highest priority thread that is ready to run from this object's waiting list.
- SharedPtr<Thread> GetHighestPriorityReadyThread();
+ SharedPtr<Thread> GetHighestPriorityReadyThread() const;
/// Get a const reference to the waiting threads list for debug use
const std::vector<SharedPtr<Thread>>& GetWaitingThreads() const;
diff --git a/src/core/hle/kernel/writable_event.h b/src/core/hle/kernel/writable_event.h
index c9068dd3d..d00c92a6b 100644
--- a/src/core/hle/kernel/writable_event.h
+++ b/src/core/hle/kernel/writable_event.h
@@ -37,7 +37,7 @@ public:
return name;
}
- static const HandleType HANDLE_TYPE = HandleType::WritableEvent;
+ static constexpr HandleType HANDLE_TYPE = HandleType::WritableEvent;
HandleType GetHandleType() const override {
return HANDLE_TYPE;
}
diff --git a/src/core/hle/result.h b/src/core/hle/result.h
index 1ed144481..8a3701151 100644
--- a/src/core/hle/result.h
+++ b/src/core/hle/result.h
@@ -13,14 +13,6 @@
// All the constants in this file come from http://switchbrew.org/index.php?title=Error_codes
/**
- * Detailed description of the error. Code 0 always means success.
- */
-enum class ErrorDescription : u32 {
- Success = 0,
- RemoteProcessDead = 301,
-};
-
-/**
* Identifies the module which caused the error. Error codes can be propagated through a call
* chain, meaning that this doesn't always correspond to the module where the API call made is
* contained.
@@ -120,30 +112,18 @@ enum class ErrorModule : u32 {
ShopN = 811,
};
-/// Encapsulates a CTR-OS error code, allowing it to be separated into its constituent fields.
+/// Encapsulates a Horizon OS error code, allowing it to be separated into its constituent fields.
union ResultCode {
u32 raw;
BitField<0, 9, ErrorModule> module;
BitField<9, 13, u32> description;
- // The last bit of `level` is checked by apps and the kernel to determine if a result code is an
- // error
- BitField<31, 1, u32> is_error;
-
constexpr explicit ResultCode(u32 raw) : raw(raw) {}
- constexpr ResultCode(ErrorModule module, ErrorDescription description)
- : ResultCode(module, static_cast<u32>(description)) {}
-
constexpr ResultCode(ErrorModule module_, u32 description_)
: raw(module.FormatValue(module_) | description.FormatValue(description_)) {}
- constexpr ResultCode& operator=(const ResultCode& o) {
- raw = o.raw;
- return *this;
- }
-
constexpr bool IsSuccess() const {
return raw == 0;
}
diff --git a/src/core/hle/service/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp
index 1f8ed265e..025714e5a 100644
--- a/src/core/hle/service/acc/acc.cpp
+++ b/src/core/hle/service/acc/acc.cpp
@@ -10,31 +10,23 @@
#include "common/logging/log.h"
#include "common/string_util.h"
#include "common/swap.h"
+#include "core/constants.h"
#include "core/core_timing.h"
+#include "core/file_sys/control_metadata.h"
+#include "core/file_sys/patch_manager.h"
#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/process.h"
#include "core/hle/service/acc/acc.h"
#include "core/hle/service/acc/acc_aa.h"
#include "core/hle/service/acc/acc_su.h"
#include "core/hle/service/acc/acc_u0.h"
#include "core/hle/service/acc/acc_u1.h"
#include "core/hle/service/acc/profile_manager.h"
+#include "core/loader/loader.h"
namespace Service::Account {
-// Smallest JPEG https://github.com/mathiasbynens/small/blob/master/jpeg.jpg
-// used as a backup should the one on disk not exist
-constexpr u32 backup_jpeg_size = 107;
-constexpr std::array<u8, backup_jpeg_size> backup_jpeg{{
- 0xff, 0xd8, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x02, 0x02,
- 0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x06, 0x04, 0x04, 0x04, 0x04, 0x04, 0x08, 0x06, 0x06, 0x05,
- 0x06, 0x09, 0x08, 0x0a, 0x0a, 0x09, 0x08, 0x09, 0x09, 0x0a, 0x0c, 0x0f, 0x0c, 0x0a, 0x0b, 0x0e,
- 0x0b, 0x09, 0x09, 0x0d, 0x11, 0x0d, 0x0e, 0x0f, 0x10, 0x10, 0x11, 0x10, 0x0a, 0x0c, 0x12, 0x13,
- 0x12, 0x10, 0x13, 0x0f, 0x10, 0x10, 0x10, 0xff, 0xc9, 0x00, 0x0b, 0x08, 0x00, 0x01, 0x00, 0x01,
- 0x01, 0x01, 0x11, 0x00, 0xff, 0xcc, 0x00, 0x06, 0x00, 0x10, 0x10, 0x05, 0xff, 0xda, 0x00, 0x08,
- 0x01, 0x01, 0x00, 0x00, 0x3f, 0x00, 0xd2, 0xcf, 0x20, 0xff, 0xd9,
-}};
-
-static std::string GetImagePath(UUID uuid) {
+static std::string GetImagePath(Common::UUID uuid) {
return FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
"/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg";
}
@@ -46,7 +38,7 @@ static constexpr u32 SanitizeJPEGSize(std::size_t size) {
class IProfile final : public ServiceFramework<IProfile> {
public:
- explicit IProfile(UUID user_id, ProfileManager& profile_manager)
+ explicit IProfile(Common::UUID user_id, ProfileManager& profile_manager)
: ServiceFramework("IProfile"), profile_manager(profile_manager), user_id(user_id) {
static const FunctionInfo functions[] = {
{0, &IProfile::Get, "Get"},
@@ -101,8 +93,8 @@ private:
if (!image.IsOpen()) {
LOG_WARNING(Service_ACC,
"Failed to load user provided image! Falling back to built-in backup...");
- ctx.WriteBuffer(backup_jpeg);
- rb.Push<u32>(backup_jpeg_size);
+ ctx.WriteBuffer(Core::Constants::ACCOUNT_BACKUP_JPEG);
+ rb.Push<u32>(Core::Constants::ACCOUNT_BACKUP_JPEG.size());
return;
}
@@ -124,19 +116,20 @@ private:
if (!image.IsOpen()) {
LOG_WARNING(Service_ACC,
"Failed to load user provided image! Falling back to built-in backup...");
- rb.Push<u32>(backup_jpeg_size);
+ rb.Push<u32>(Core::Constants::ACCOUNT_BACKUP_JPEG.size());
} else {
rb.Push<u32>(SanitizeJPEGSize(image.GetSize()));
}
}
const ProfileManager& profile_manager;
- UUID user_id; ///< The user id this profile refers to.
+ Common::UUID user_id; ///< The user id this profile refers to.
};
class IManagerForApplication final : public ServiceFramework<IManagerForApplication> {
public:
IManagerForApplication() : ServiceFramework("IManagerForApplication") {
+ // clang-format off
static const FunctionInfo functions[] = {
{0, &IManagerForApplication::CheckAvailability, "CheckAvailability"},
{1, &IManagerForApplication::GetAccountId, "GetAccountId"},
@@ -145,7 +138,10 @@ public:
{130, nullptr, "GetNintendoAccountUserResourceCacheForApplication"},
{150, nullptr, "CreateAuthorizationRequest"},
{160, nullptr, "StoreOpenContext"},
+ {170, nullptr, "LoadNetworkServiceLicenseKindAsync"},
};
+ // clang-format on
+
RegisterHandlers(functions);
}
@@ -175,7 +171,7 @@ void Module::Interface::GetUserCount(Kernel::HLERequestContext& ctx) {
void Module::Interface::GetUserExistence(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- UUID user_id = rp.PopRaw<UUID>();
+ Common::UUID user_id = rp.PopRaw<Common::UUID>();
LOG_INFO(Service_ACC, "called user_id={}", user_id.Format());
IPC::ResponseBuilder rb{ctx, 3};
@@ -201,12 +197,12 @@ void Module::Interface::GetLastOpenedUser(Kernel::HLERequestContext& ctx) {
LOG_INFO(Service_ACC, "called");
IPC::ResponseBuilder rb{ctx, 6};
rb.Push(RESULT_SUCCESS);
- rb.PushRaw<UUID>(profile_manager->GetLastOpenedUser());
+ rb.PushRaw<Common::UUID>(profile_manager->GetLastOpenedUser());
}
void Module::Interface::GetProfile(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- UUID user_id = rp.PopRaw<UUID>();
+ Common::UUID user_id = rp.PopRaw<Common::UUID>();
LOG_DEBUG(Service_ACC, "called user_id={}", user_id.Format());
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
@@ -221,7 +217,7 @@ void Module::Interface::IsUserRegistrationRequestPermitted(Kernel::HLERequestCon
rb.Push(profile_manager->CanSystemRegisterUser());
}
-void Module::Interface::InitializeApplicationInfo(Kernel::HLERequestContext& ctx) {
+void Module::Interface::InitializeApplicationInfoOld(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_ACC, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -234,6 +230,31 @@ void Module::Interface::GetBaasAccountManagerForApplication(Kernel::HLERequestCo
rb.PushIpcInterface<IManagerForApplication>();
}
+void Module::Interface::IsUserAccountSwitchLocked(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_ACC, "called");
+ FileSys::NACP nacp;
+ const auto res = system.GetAppLoader().ReadControlData(nacp);
+
+ bool is_locked = false;
+
+ if (res != Loader::ResultStatus::Success) {
+ FileSys::PatchManager pm{system.CurrentProcess()->GetTitleID()};
+ auto nacp_unique = pm.GetControlMetadata().first;
+
+ if (nacp_unique != nullptr) {
+ is_locked = nacp_unique->GetUserAccountSwitchLock();
+ } else {
+ LOG_ERROR(Service_ACC, "nacp_unique is null!");
+ }
+ } else {
+ is_locked = nacp.GetUserAccountSwitchLock();
+ }
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(is_locked);
+}
+
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
@@ -241,15 +262,15 @@ void Module::Interface::TrySelectUserWithoutInteraction(Kernel::HLERequestContex
IPC::ResponseBuilder rb{ctx, 6};
if (profile_manager->GetUserCount() != 1) {
rb.Push(RESULT_SUCCESS);
- rb.PushRaw<u128>(INVALID_UUID);
+ rb.PushRaw<u128>(Common::INVALID_UUID);
return;
}
const auto user_list = profile_manager->GetAllUsers();
if (std::all_of(user_list.begin(), user_list.end(),
- [](const auto& user) { return user.uuid == INVALID_UUID; })) {
+ [](const auto& user) { return user.uuid == Common::INVALID_UUID; })) {
rb.Push(ResultCode(-1)); // TODO(ogniK): Find the correct error code
- rb.PushRaw<u128>(INVALID_UUID);
+ rb.PushRaw<u128>(Common::INVALID_UUID);
return;
}
@@ -259,19 +280,25 @@ void Module::Interface::TrySelectUserWithoutInteraction(Kernel::HLERequestContex
}
Module::Interface::Interface(std::shared_ptr<Module> module,
- std::shared_ptr<ProfileManager> profile_manager, const char* name)
+ std::shared_ptr<ProfileManager> profile_manager, Core::System& system,
+ const char* name)
: ServiceFramework(name), module(std::move(module)),
- profile_manager(std::move(profile_manager)) {}
+ profile_manager(std::move(profile_manager)), system(system) {}
Module::Interface::~Interface() = default;
-void InstallInterfaces(SM::ServiceManager& service_manager) {
+void InstallInterfaces(Core::System& system) {
auto module = std::make_shared<Module>();
auto profile_manager = std::make_shared<ProfileManager>();
- std::make_shared<ACC_AA>(module, profile_manager)->InstallAsService(service_manager);
- std::make_shared<ACC_SU>(module, profile_manager)->InstallAsService(service_manager);
- std::make_shared<ACC_U0>(module, profile_manager)->InstallAsService(service_manager);
- std::make_shared<ACC_U1>(module, profile_manager)->InstallAsService(service_manager);
+
+ std::make_shared<ACC_AA>(module, profile_manager, system)
+ ->InstallAsService(system.ServiceManager());
+ std::make_shared<ACC_SU>(module, profile_manager, system)
+ ->InstallAsService(system.ServiceManager());
+ std::make_shared<ACC_U0>(module, profile_manager, system)
+ ->InstallAsService(system.ServiceManager());
+ std::make_shared<ACC_U1>(module, profile_manager, system)
+ ->InstallAsService(system.ServiceManager());
}
} // namespace Service::Account
diff --git a/src/core/hle/service/acc/acc.h b/src/core/hle/service/acc/acc.h
index 89b2104fa..350f123a0 100644
--- a/src/core/hle/service/acc/acc.h
+++ b/src/core/hle/service/acc/acc.h
@@ -15,7 +15,8 @@ public:
class Interface : public ServiceFramework<Interface> {
public:
explicit Interface(std::shared_ptr<Module> module,
- std::shared_ptr<ProfileManager> profile_manager, const char* name);
+ std::shared_ptr<ProfileManager> profile_manager, Core::System& system,
+ const char* name);
~Interface() override;
void GetUserCount(Kernel::HLERequestContext& ctx);
@@ -24,18 +25,20 @@ public:
void ListOpenUsers(Kernel::HLERequestContext& ctx);
void GetLastOpenedUser(Kernel::HLERequestContext& ctx);
void GetProfile(Kernel::HLERequestContext& ctx);
- void InitializeApplicationInfo(Kernel::HLERequestContext& ctx);
+ void InitializeApplicationInfoOld(Kernel::HLERequestContext& ctx);
void GetBaasAccountManagerForApplication(Kernel::HLERequestContext& ctx);
void IsUserRegistrationRequestPermitted(Kernel::HLERequestContext& ctx);
void TrySelectUserWithoutInteraction(Kernel::HLERequestContext& ctx);
+ void IsUserAccountSwitchLocked(Kernel::HLERequestContext& ctx);
protected:
std::shared_ptr<Module> module;
std::shared_ptr<ProfileManager> profile_manager;
+ Core::System& system;
};
};
/// Registers all ACC services with the specified service manager.
-void InstallInterfaces(SM::ServiceManager& service_manager);
+void InstallInterfaces(Core::System& system);
} // namespace Service::Account
diff --git a/src/core/hle/service/acc/acc_aa.cpp b/src/core/hle/service/acc/acc_aa.cpp
index e84d9f7cf..3bac6bcd1 100644
--- a/src/core/hle/service/acc/acc_aa.cpp
+++ b/src/core/hle/service/acc/acc_aa.cpp
@@ -6,8 +6,9 @@
namespace Service::Account {
-ACC_AA::ACC_AA(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager)
- : Module::Interface(std::move(module), std::move(profile_manager), "acc:aa") {
+ACC_AA::ACC_AA(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager,
+ Core::System& system)
+ : Module::Interface(std::move(module), std::move(profile_manager), system, "acc:aa") {
static const FunctionInfo functions[] = {
{0, nullptr, "EnsureCacheAsync"},
{1, nullptr, "LoadCache"},
diff --git a/src/core/hle/service/acc/acc_aa.h b/src/core/hle/service/acc/acc_aa.h
index 9edb0421b..932c04890 100644
--- a/src/core/hle/service/acc/acc_aa.h
+++ b/src/core/hle/service/acc/acc_aa.h
@@ -10,8 +10,8 @@ namespace Service::Account {
class ACC_AA final : public Module::Interface {
public:
- explicit ACC_AA(std::shared_ptr<Module> module,
- std::shared_ptr<ProfileManager> profile_manager);
+ explicit ACC_AA(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager,
+ Core::System& system);
~ACC_AA() override;
};
diff --git a/src/core/hle/service/acc/acc_su.cpp b/src/core/hle/service/acc/acc_su.cpp
index 5e2030355..1b7ec3ed0 100644
--- a/src/core/hle/service/acc/acc_su.cpp
+++ b/src/core/hle/service/acc/acc_su.cpp
@@ -6,8 +6,10 @@
namespace Service::Account {
-ACC_SU::ACC_SU(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager)
- : Module::Interface(std::move(module), std::move(profile_manager), "acc:su") {
+ACC_SU::ACC_SU(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager,
+ Core::System& system)
+ : Module::Interface(std::move(module), std::move(profile_manager), system, "acc:su") {
+ // clang-format off
static const FunctionInfo functions[] = {
{0, &ACC_SU::GetUserCount, "GetUserCount"},
{1, &ACC_SU::GetUserExistence, "GetUserExistence"},
@@ -19,6 +21,7 @@ ACC_SU::ACC_SU(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> p
{50, &ACC_SU::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"},
{51, &ACC_SU::TrySelectUserWithoutInteraction, "TrySelectUserWithoutInteraction"},
{60, nullptr, "ListOpenContextStoredUsers"},
+ {99, nullptr, "DebugActivateOpenContextRetention"},
{100, nullptr, "GetUserRegistrationNotifier"},
{101, nullptr, "GetUserStateChangeNotifier"},
{102, nullptr, "GetBaasAccountManagerForSystemService"},
@@ -29,6 +32,8 @@ ACC_SU::ACC_SU(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> p
{111, nullptr, "ClearSaveDataThumbnail"},
{112, nullptr, "LoadSaveDataThumbnail"},
{113, nullptr, "GetSaveDataThumbnailExistence"},
+ {130, nullptr, "ActivateOpenContextRetention"},
+ {140, nullptr, "ListQualifiedUsers"},
{190, nullptr, "GetUserLastOpenedApplication"},
{191, nullptr, "ActivateOpenContextHolder"},
{200, nullptr, "BeginUserRegistration"},
@@ -48,6 +53,8 @@ ACC_SU::ACC_SU(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> p
{998, nullptr, "DebugSetUserStateClose"},
{999, nullptr, "DebugSetUserStateOpen"},
};
+ // clang-format on
+
RegisterHandlers(functions);
}
diff --git a/src/core/hle/service/acc/acc_su.h b/src/core/hle/service/acc/acc_su.h
index fcced063a..0a700d9bf 100644
--- a/src/core/hle/service/acc/acc_su.h
+++ b/src/core/hle/service/acc/acc_su.h
@@ -10,8 +10,8 @@ namespace Service::Account {
class ACC_SU final : public Module::Interface {
public:
- explicit ACC_SU(std::shared_ptr<Module> module,
- std::shared_ptr<ProfileManager> profile_manager);
+ explicit ACC_SU(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager,
+ Core::System& system);
~ACC_SU() override;
};
diff --git a/src/core/hle/service/acc/acc_u0.cpp b/src/core/hle/service/acc/acc_u0.cpp
index a4d705b45..2f239e8c0 100644
--- a/src/core/hle/service/acc/acc_u0.cpp
+++ b/src/core/hle/service/acc/acc_u0.cpp
@@ -6,8 +6,10 @@
namespace Service::Account {
-ACC_U0::ACC_U0(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager)
- : Module::Interface(std::move(module), std::move(profile_manager), "acc:u0") {
+ACC_U0::ACC_U0(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager,
+ Core::System& system)
+ : Module::Interface(std::move(module), std::move(profile_manager), system, "acc:u0") {
+ // clang-format off
static const FunctionInfo functions[] = {
{0, &ACC_U0::GetUserCount, "GetUserCount"},
{1, &ACC_U0::GetUserExistence, "GetUserExistence"},
@@ -19,7 +21,8 @@ ACC_U0::ACC_U0(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> p
{50, &ACC_U0::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"},
{51, &ACC_U0::TrySelectUserWithoutInteraction, "TrySelectUserWithoutInteraction"},
{60, nullptr, "ListOpenContextStoredUsers"},
- {100, &ACC_U0::InitializeApplicationInfo, "InitializeApplicationInfo"},
+ {99, nullptr, "DebugActivateOpenContextRetention"},
+ {100, &ACC_U0::InitializeApplicationInfoOld, "InitializeApplicationInfoOld"},
{101, &ACC_U0::GetBaasAccountManagerForApplication, "GetBaasAccountManagerForApplication"},
{102, nullptr, "AuthenticateApplicationAsync"},
{103, nullptr, "CheckNetworkServiceAvailabilityAsync"},
@@ -27,7 +30,13 @@ ACC_U0::ACC_U0(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> p
{111, nullptr, "ClearSaveDataThumbnail"},
{120, nullptr, "CreateGuestLoginRequest"},
{130, nullptr, "LoadOpenContext"},
+ {131, nullptr, "ListOpenContextStoredUsers"},
+ {140, nullptr, "InitializeApplicationInfo"},
+ {141, nullptr, "ListQualifiedUsers"},
+ {150, &ACC_U0::IsUserAccountSwitchLocked, "IsUserAccountSwitchLocked"},
};
+ // clang-format on
+
RegisterHandlers(functions);
}
diff --git a/src/core/hle/service/acc/acc_u0.h b/src/core/hle/service/acc/acc_u0.h
index a1290e0bd..3bd9c3164 100644
--- a/src/core/hle/service/acc/acc_u0.h
+++ b/src/core/hle/service/acc/acc_u0.h
@@ -10,8 +10,8 @@ namespace Service::Account {
class ACC_U0 final : public Module::Interface {
public:
- explicit ACC_U0(std::shared_ptr<Module> module,
- std::shared_ptr<ProfileManager> profile_manager);
+ explicit ACC_U0(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager,
+ Core::System& system);
~ACC_U0() override;
};
diff --git a/src/core/hle/service/acc/acc_u1.cpp b/src/core/hle/service/acc/acc_u1.cpp
index 8fffc93b5..6520b3968 100644
--- a/src/core/hle/service/acc/acc_u1.cpp
+++ b/src/core/hle/service/acc/acc_u1.cpp
@@ -6,8 +6,10 @@
namespace Service::Account {
-ACC_U1::ACC_U1(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager)
- : Module::Interface(std::move(module), std::move(profile_manager), "acc:u1") {
+ACC_U1::ACC_U1(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager,
+ Core::System& system)
+ : Module::Interface(std::move(module), std::move(profile_manager), system, "acc:u1") {
+ // clang-format off
static const FunctionInfo functions[] = {
{0, &ACC_U1::GetUserCount, "GetUserCount"},
{1, &ACC_U1::GetUserExistence, "GetUserExistence"},
@@ -19,6 +21,7 @@ ACC_U1::ACC_U1(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> p
{50, &ACC_U1::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"},
{51, &ACC_U1::TrySelectUserWithoutInteraction, "TrySelectUserWithoutInteraction"},
{60, nullptr, "ListOpenContextStoredUsers"},
+ {99, nullptr, "DebugActivateOpenContextRetention"},
{100, nullptr, "GetUserRegistrationNotifier"},
{101, nullptr, "GetUserStateChangeNotifier"},
{102, nullptr, "GetBaasAccountManagerForSystemService"},
@@ -29,12 +32,16 @@ ACC_U1::ACC_U1(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> p
{111, nullptr, "ClearSaveDataThumbnail"},
{112, nullptr, "LoadSaveDataThumbnail"},
{113, nullptr, "GetSaveDataThumbnailExistence"},
+ {130, nullptr, "ActivateOpenContextRetention"},
+ {140, nullptr, "ListQualifiedUsers"},
{190, nullptr, "GetUserLastOpenedApplication"},
{191, nullptr, "ActivateOpenContextHolder"},
{997, nullptr, "DebugInvalidateTokenCacheForUser"},
{998, nullptr, "DebugSetUserStateClose"},
{999, nullptr, "DebugSetUserStateOpen"},
};
+ // clang-format on
+
RegisterHandlers(functions);
}
diff --git a/src/core/hle/service/acc/acc_u1.h b/src/core/hle/service/acc/acc_u1.h
index 9e79daee3..829f8a744 100644
--- a/src/core/hle/service/acc/acc_u1.h
+++ b/src/core/hle/service/acc/acc_u1.h
@@ -10,8 +10,8 @@ namespace Service::Account {
class ACC_U1 final : public Module::Interface {
public:
- explicit ACC_U1(std::shared_ptr<Module> module,
- std::shared_ptr<ProfileManager> profile_manager);
+ explicit ACC_U1(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager,
+ Core::System& system);
~ACC_U1() override;
};
diff --git a/src/core/hle/service/acc/profile_manager.cpp b/src/core/hle/service/acc/profile_manager.cpp
index 1316d0b07..49aa5908b 100644
--- a/src/core/hle/service/acc/profile_manager.cpp
+++ b/src/core/hle/service/acc/profile_manager.cpp
@@ -13,6 +13,8 @@
namespace Service::Account {
+using Common::UUID;
+
struct UserRaw {
UUID uuid;
UUID uuid2;
@@ -35,26 +37,6 @@ constexpr ResultCode ERROR_ARGUMENT_IS_NULL(ErrorModule::Account, 20);
constexpr char ACC_SAVE_AVATORS_BASE_PATH[] = "/system/save/8000000000000010/su/avators/";
-UUID UUID::Generate() {
- std::random_device device;
- std::mt19937 gen(device());
- std::uniform_int_distribution<u64> distribution(1, std::numeric_limits<u64>::max());
- return UUID{distribution(gen), distribution(gen)};
-}
-
-std::string UUID::Format() const {
- return fmt::format("0x{:016X}{:016X}", uuid[1], uuid[0]);
-}
-
-std::string UUID::FormatSwitch() const {
- std::array<u8, 16> s{};
- std::memcpy(s.data(), uuid.data(), sizeof(u128));
- return fmt::format("{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{"
- ":02x}{:02x}{:02x}{:02x}{:02x}",
- s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7], s[8], s[9], s[10], s[11],
- s[12], s[13], s[14], s[15]);
-}
-
ProfileManager::ProfileManager() {
ParseUserSaveFile();
@@ -217,7 +199,7 @@ bool ProfileManager::UserExists(UUID uuid) const {
bool ProfileManager::UserExistsIndex(std::size_t index) const {
if (index >= MAX_USERS)
return false;
- return profiles[index].user_uuid.uuid != INVALID_UUID;
+ return profiles[index].user_uuid.uuid != Common::INVALID_UUID;
}
/// Opens a specific user
@@ -311,7 +293,7 @@ bool ProfileManager::RemoveUser(UUID uuid) {
bool ProfileManager::SetProfileBase(UUID uuid, const ProfileBase& profile_new) {
const auto index = GetUserIndex(uuid);
- if (!index || profile_new.user_uuid == UUID(INVALID_UUID)) {
+ if (!index || profile_new.user_uuid == UUID(Common::INVALID_UUID)) {
return false;
}
@@ -342,7 +324,7 @@ void ProfileManager::ParseUserSaveFile() {
}
for (const auto& user : data.users) {
- if (user.uuid == UUID(INVALID_UUID)) {
+ if (user.uuid == UUID(Common::INVALID_UUID)) {
continue;
}
diff --git a/src/core/hle/service/acc/profile_manager.h b/src/core/hle/service/acc/profile_manager.h
index c4ce2e0b3..fd7abb541 100644
--- a/src/core/hle/service/acc/profile_manager.h
+++ b/src/core/hle/service/acc/profile_manager.h
@@ -9,47 +9,15 @@
#include "common/common_types.h"
#include "common/swap.h"
+#include "common/uuid.h"
#include "core/hle/result.h"
namespace Service::Account {
constexpr std::size_t MAX_USERS = 8;
-constexpr u128 INVALID_UUID{{0, 0}};
-
-struct UUID {
- // UUIDs which are 0 are considered invalid!
- u128 uuid = INVALID_UUID;
- UUID() = default;
- explicit UUID(const u128& id) : uuid{id} {}
- explicit UUID(const u64 lo, const u64 hi) : uuid{{lo, hi}} {}
-
- explicit operator bool() const {
- return uuid != INVALID_UUID;
- }
-
- bool operator==(const UUID& rhs) const {
- return uuid == rhs.uuid;
- }
-
- bool operator!=(const UUID& rhs) const {
- return !operator==(rhs);
- }
-
- // TODO(ogniK): Properly generate uuids based on RFC-4122
- static UUID Generate();
-
- // Set the UUID to {0,0} to be considered an invalid user
- void Invalidate() {
- uuid = INVALID_UUID;
- }
-
- std::string Format() const;
- std::string FormatSwitch() const;
-};
-static_assert(sizeof(UUID) == 16, "UUID is an invalid size!");
constexpr std::size_t profile_username_size = 32;
using ProfileUsername = std::array<u8, profile_username_size>;
-using UserIDArray = std::array<UUID, MAX_USERS>;
+using UserIDArray = std::array<Common::UUID, MAX_USERS>;
/// Contains extra data related to a user.
/// TODO: RE this structure
@@ -66,7 +34,7 @@ static_assert(sizeof(ProfileData) == 0x80, "ProfileData structure has incorrect
/// This holds general information about a users profile. This is where we store all the information
/// based on a specific user
struct ProfileInfo {
- UUID user_uuid;
+ Common::UUID user_uuid;
ProfileUsername username;
u64 creation_time;
ProfileData data; // TODO(ognik): Work out what this is
@@ -74,7 +42,7 @@ struct ProfileInfo {
};
struct ProfileBase {
- UUID user_uuid;
+ Common::UUID user_uuid;
u64_le timestamp;
ProfileUsername username;
@@ -96,33 +64,33 @@ public:
~ProfileManager();
ResultCode AddUser(const ProfileInfo& user);
- ResultCode CreateNewUser(UUID uuid, const ProfileUsername& username);
- ResultCode CreateNewUser(UUID uuid, const std::string& username);
- std::optional<UUID> GetUser(std::size_t index) const;
- std::optional<std::size_t> GetUserIndex(const UUID& uuid) const;
+ ResultCode CreateNewUser(Common::UUID uuid, const ProfileUsername& username);
+ ResultCode CreateNewUser(Common::UUID uuid, const std::string& username);
+ std::optional<Common::UUID> GetUser(std::size_t index) const;
+ std::optional<std::size_t> GetUserIndex(const Common::UUID& uuid) const;
std::optional<std::size_t> GetUserIndex(const ProfileInfo& user) const;
bool GetProfileBase(std::optional<std::size_t> index, ProfileBase& profile) const;
- bool GetProfileBase(UUID uuid, ProfileBase& profile) const;
+ bool GetProfileBase(Common::UUID uuid, ProfileBase& profile) const;
bool GetProfileBase(const ProfileInfo& user, ProfileBase& profile) const;
bool GetProfileBaseAndData(std::optional<std::size_t> index, ProfileBase& profile,
ProfileData& data) const;
- bool GetProfileBaseAndData(UUID uuid, ProfileBase& profile, ProfileData& data) const;
+ bool GetProfileBaseAndData(Common::UUID uuid, ProfileBase& profile, ProfileData& data) const;
bool GetProfileBaseAndData(const ProfileInfo& user, ProfileBase& profile,
ProfileData& data) const;
std::size_t GetUserCount() const;
std::size_t GetOpenUserCount() const;
- bool UserExists(UUID uuid) const;
+ bool UserExists(Common::UUID uuid) const;
bool UserExistsIndex(std::size_t index) const;
- void OpenUser(UUID uuid);
- void CloseUser(UUID uuid);
+ void OpenUser(Common::UUID uuid);
+ void CloseUser(Common::UUID uuid);
UserIDArray GetOpenUsers() const;
UserIDArray GetAllUsers() const;
- UUID GetLastOpenedUser() const;
+ Common::UUID GetLastOpenedUser() const;
bool CanSystemRegisterUser() const;
- bool RemoveUser(UUID uuid);
- bool SetProfileBase(UUID uuid, const ProfileBase& profile_new);
+ bool RemoveUser(Common::UUID uuid);
+ bool SetProfileBase(Common::UUID uuid, const ProfileBase& profile_new);
private:
void ParseUserSaveFile();
@@ -132,7 +100,7 @@ private:
std::array<ProfileInfo, MAX_USERS> profiles{};
std::size_t user_count = 0;
- UUID last_opened_user{INVALID_UUID};
+ Common::UUID last_opened_user{Common::INVALID_UUID};
};
}; // namespace Service::Account
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index 3f009d2b7..4a7bf4acb 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -2,18 +2,20 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <algorithm>
#include <array>
#include <cinttypes>
#include <cstring>
-#include <stack>
#include "audio_core/audio_renderer.h"
#include "core/core.h"
+#include "core/file_sys/control_metadata.h"
+#include "core/file_sys/patch_manager.h"
#include "core/file_sys/savedata_factory.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/readable_event.h"
-#include "core/hle/kernel/shared_memory.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"
@@ -22,7 +24,6 @@
#include "core/hle/service/am/applets/applets.h"
#include "core/hle/service/am/applets/profile_select.h"
#include "core/hle/service/am/applets/software_keyboard.h"
-#include "core/hle/service/am/applets/stub_applet.h"
#include "core/hle/service/am/applets/web_browser.h"
#include "core/hle/service/am/idle.h"
#include "core/hle/service/am/omm.h"
@@ -30,9 +31,11 @@
#include "core/hle/service/am/tcap.h"
#include "core/hle/service/apm/apm.h"
#include "core/hle/service/filesystem/filesystem.h"
+#include "core/hle/service/ns/ns.h"
#include "core/hle/service/nvflinger/nvflinger.h"
#include "core/hle/service/pm/pm.h"
#include "core/hle/service/set/set.h"
+#include "core/hle/service/sm/sm.h"
#include "core/hle/service/vi/vi.h"
#include "core/settings.h"
@@ -42,12 +45,6 @@ constexpr ResultCode ERR_NO_DATA_IN_CHANNEL{ErrorModule::AM, 0x2};
constexpr ResultCode ERR_NO_MESSAGES{ErrorModule::AM, 0x3};
constexpr ResultCode ERR_SIZE_OUT_OF_BOUNDS{ErrorModule::AM, 0x1F7};
-enum class AppletId : u32 {
- ProfileSelect = 0x10,
- SoftwareKeyboard = 0x11,
- LibAppletOff = 0x17,
-};
-
constexpr u32 POP_LAUNCH_PARAMETER_MAGIC = 0xC79497CA;
struct LaunchParameters {
@@ -93,38 +90,84 @@ void IWindowController::AcquireForegroundRights(Kernel::HLERequestContext& ctx)
}
IAudioController::IAudioController() : ServiceFramework("IAudioController") {
+ // clang-format off
static const FunctionInfo functions[] = {
{0, &IAudioController::SetExpectedMasterVolume, "SetExpectedMasterVolume"},
- {1, &IAudioController::GetMainAppletExpectedMasterVolume,
- "GetMainAppletExpectedMasterVolume"},
- {2, &IAudioController::GetLibraryAppletExpectedMasterVolume,
- "GetLibraryAppletExpectedMasterVolume"},
- {3, nullptr, "ChangeMainAppletMasterVolume"},
- {4, nullptr, "SetTransparentVolumeRate"},
+ {1, &IAudioController::GetMainAppletExpectedMasterVolume, "GetMainAppletExpectedMasterVolume"},
+ {2, &IAudioController::GetLibraryAppletExpectedMasterVolume, "GetLibraryAppletExpectedMasterVolume"},
+ {3, &IAudioController::ChangeMainAppletMasterVolume, "ChangeMainAppletMasterVolume"},
+ {4, &IAudioController::SetTransparentAudioRate, "SetTransparentVolumeRate"},
};
+ // clang-format on
+
RegisterHandlers(functions);
}
IAudioController::~IAudioController() = default;
void IAudioController::SetExpectedMasterVolume(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_AM, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ const float main_applet_volume_tmp = rp.Pop<float>();
+ const float library_applet_volume_tmp = rp.Pop<float>();
+
+ LOG_DEBUG(Service_AM, "called. main_applet_volume={}, library_applet_volume={}",
+ main_applet_volume_tmp, library_applet_volume_tmp);
+
+ // Ensure the volume values remain within the 0-100% range
+ main_applet_volume = std::clamp(main_applet_volume_tmp, min_allowed_volume, max_allowed_volume);
+ library_applet_volume =
+ std::clamp(library_applet_volume_tmp, min_allowed_volume, max_allowed_volume);
+
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void IAudioController::GetMainAppletExpectedMasterVolume(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_AM, "(STUBBED) called");
+ LOG_DEBUG(Service_AM, "called. main_applet_volume={}", main_applet_volume);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- rb.Push(volume);
+ rb.Push(main_applet_volume);
}
void IAudioController::GetLibraryAppletExpectedMasterVolume(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_AM, "(STUBBED) called");
+ LOG_DEBUG(Service_AM, "called. library_applet_volume={}", library_applet_volume);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- rb.Push(volume);
+ rb.Push(library_applet_volume);
+}
+
+void IAudioController::ChangeMainAppletMasterVolume(Kernel::HLERequestContext& ctx) {
+ struct Parameters {
+ float volume;
+ s64 fade_time_ns;
+ };
+ static_assert(sizeof(Parameters) == 16);
+
+ IPC::RequestParser rp{ctx};
+ const auto parameters = rp.PopRaw<Parameters>();
+
+ LOG_DEBUG(Service_AM, "called. volume={}, fade_time_ns={}", parameters.volume,
+ parameters.fade_time_ns);
+
+ main_applet_volume = std::clamp(parameters.volume, min_allowed_volume, max_allowed_volume);
+ fade_time_ns = std::chrono::nanoseconds{parameters.fade_time_ns};
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
+void IAudioController::SetTransparentAudioRate(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const float transparent_volume_rate_tmp = rp.Pop<float>();
+
+ LOG_DEBUG(Service_AM, "called. transparent_volume_rate={}", transparent_volume_rate_tmp);
+
+ // Clamp volume range to 0-100%.
+ transparent_volume_rate =
+ std::clamp(transparent_volume_rate_tmp, min_allowed_volume, max_allowed_volume);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
}
IDisplayController::IDisplayController() : ServiceFramework("IDisplayController") {
@@ -169,7 +212,22 @@ IDisplayController::IDisplayController() : ServiceFramework("IDisplayController"
IDisplayController::~IDisplayController() = default;
-IDebugFunctions::IDebugFunctions() : ServiceFramework("IDebugFunctions") {}
+IDebugFunctions::IDebugFunctions() : ServiceFramework{"IDebugFunctions"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "NotifyMessageToHomeMenuForDebug"},
+ {1, nullptr, "OpenMainApplication"},
+ {10, nullptr, "EmulateButtonEvent"},
+ {20, nullptr, "InvalidateTransitionLayer"},
+ {30, nullptr, "RequestLaunchApplicationWithUserAndArgumentForDebug"},
+ {40, nullptr, "GetAppletResourceUsageInfo"},
+ {41, nullptr, "SetCpuBoostModeForApplet"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+}
+
IDebugFunctions::~IDebugFunctions() = default;
ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger)
@@ -179,8 +237,8 @@ ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger
{0, nullptr, "Exit"},
{1, &ISelfController::LockExit, "LockExit"},
{2, &ISelfController::UnlockExit, "UnlockExit"},
- {3, nullptr, "EnterFatalSection"},
- {4, nullptr, "LeaveFatalSection"},
+ {3, &ISelfController::EnterFatalSection, "EnterFatalSection"},
+ {4, &ISelfController::LeaveFatalSection, "LeaveFatalSection"},
{9, &ISelfController::GetLibraryAppletLaunchableEvent, "GetLibraryAppletLaunchableEvent"},
{10, &ISelfController::SetScreenShotPermission, "SetScreenShotPermission"},
{11, &ISelfController::SetOperationModeChangedNotification, "SetOperationModeChangedNotification"},
@@ -196,6 +254,7 @@ ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger
{40, &ISelfController::CreateManagedDisplayLayer, "CreateManagedDisplayLayer"},
{41, nullptr, "IsSystemBufferSharingEnabled"},
{42, nullptr, "GetSystemSharedLayerHandle"},
+ {43, nullptr, "GetSystemSharedBufferHandle"},
{50, &ISelfController::SetHandlesRequestToDisplay, "SetHandlesRequestToDisplay"},
{51, nullptr, "ApproveToDisplay"},
{60, nullptr, "OverrideAutoSleepTimeAndDimmingTime"},
@@ -209,9 +268,11 @@ ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger
{68, nullptr, "SetAutoSleepDisabled"},
{69, nullptr, "IsAutoSleepDisabled"},
{70, nullptr, "ReportMultimediaError"},
+ {71, nullptr, "GetCurrentIlluminanceEx"},
{80, nullptr, "SetWirelessPriorityMode"},
{90, nullptr, "GetAccumulatedSuspendedTickValue"},
- {91, nullptr, "GetAccumulatedSuspendedTickChangedEvent"},
+ {91, &ISelfController::GetAccumulatedSuspendedTickChangedEvent, "GetAccumulatedSuspendedTickChangedEvent"},
+ {100, nullptr, "SetAlbumImageTakenNotificationEnabled"},
{1000, nullptr, "GetDebugStorageChannel"},
};
// clang-format on
@@ -219,47 +280,65 @@ ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger
RegisterHandlers(functions);
auto& kernel = Core::System::GetInstance().Kernel();
- launchable_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Sticky,
+ launchable_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Manual,
"ISelfController:LaunchableEvent");
+
+ // TODO(ogniK): Figure out where, when and why this event gets signalled
+ accumulated_suspended_tick_changed_event = Kernel::WritableEvent::CreateEventPair(
+ kernel, Kernel::ResetType::Manual, "ISelfController:AccumulatedSuspendedTickChangedEvent");
+ accumulated_suspended_tick_changed_event.writable->Signal(); // Is signalled on creation
}
ISelfController::~ISelfController() = default;
-void ISelfController::SetFocusHandlingMode(Kernel::HLERequestContext& ctx) {
- // Takes 3 input u8s with each field located immediately after the previous
- // u8, these are bool flags. No output.
+void ISelfController::LockExit(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_AM, "(STUBBED) called");
- IPC::RequestParser rp{ctx};
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
- struct FocusHandlingModeParams {
- u8 unknown0;
- u8 unknown1;
- u8 unknown2;
- };
- auto flags = rp.PopRaw<FocusHandlingModeParams>();
+void ISelfController::UnlockExit(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
-void ISelfController::SetRestartMessageEnabled(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_AM, "(STUBBED) called");
+void ISelfController::EnterFatalSection(Kernel::HLERequestContext& ctx) {
+ ++num_fatal_sections_entered;
+ LOG_DEBUG(Service_AM, "called. Num fatal sections entered: {}", num_fatal_sections_entered);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
-void ISelfController::SetPerformanceModeChangedNotification(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
+void ISelfController::LeaveFatalSection(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_AM, "called.");
- bool flag = rp.Pop<bool>();
- LOG_WARNING(Service_AM, "(STUBBED) called flag={}", flag);
+ // Entry and exit of fatal sections must be balanced.
+ if (num_fatal_sections_entered == 0) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultCode{ErrorModule::AM, 512});
+ return;
+ }
+
+ --num_fatal_sections_entered;
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
+void ISelfController::GetLibraryAppletLaunchableEvent(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+
+ launchable_event.writable->Signal();
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushCopyObjects(launchable_event.readable);
+}
+
void ISelfController::SetScreenShotPermission(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_AM, "(STUBBED) called");
@@ -277,40 +356,52 @@ void ISelfController::SetOperationModeChangedNotification(Kernel::HLERequestCont
rb.Push(RESULT_SUCCESS);
}
-void ISelfController::SetOutOfFocusSuspendingEnabled(Kernel::HLERequestContext& ctx) {
- // Takes 3 input u8s with each field located immediately after the previous
- // u8, these are bool flags. No output.
+void ISelfController::SetPerformanceModeChangedNotification(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- bool enabled = rp.Pop<bool>();
- LOG_WARNING(Service_AM, "(STUBBED) called enabled={}", enabled);
+ bool flag = rp.Pop<bool>();
+ LOG_WARNING(Service_AM, "(STUBBED) called flag={}", flag);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
-void ISelfController::LockExit(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_AM, "(STUBBED) called");
+void ISelfController::SetFocusHandlingMode(Kernel::HLERequestContext& ctx) {
+ // Takes 3 input u8s with each field located immediately after the previous
+ // u8, these are bool flags. No output.
+ IPC::RequestParser rp{ctx};
+
+ struct FocusHandlingModeParams {
+ u8 unknown0;
+ u8 unknown1;
+ u8 unknown2;
+ };
+ const auto flags = rp.PopRaw<FocusHandlingModeParams>();
+
+ LOG_WARNING(Service_AM, "(STUBBED) called. unknown0={}, unknown1={}, unknown2={}",
+ flags.unknown0, flags.unknown1, flags.unknown2);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
-void ISelfController::UnlockExit(Kernel::HLERequestContext& ctx) {
+void ISelfController::SetRestartMessageEnabled(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_AM, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
-void ISelfController::GetLibraryAppletLaunchableEvent(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_AM, "(STUBBED) called");
+void ISelfController::SetOutOfFocusSuspendingEnabled(Kernel::HLERequestContext& ctx) {
+ // Takes 3 input u8s with each field located immediately after the previous
+ // u8, these are bool flags. No output.
+ IPC::RequestParser rp{ctx};
- launchable_event.writable->Signal();
+ bool enabled = rp.Pop<bool>();
+ LOG_WARNING(Service_AM, "(STUBBED) called enabled={}", enabled);
- IPC::ResponseBuilder rb{ctx, 2, 1};
+ IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- rb.PushCopyObjects(launchable_event.readable);
}
void ISelfController::SetScreenShotImageOrientation(Kernel::HLERequestContext& ctx) {
@@ -358,12 +449,23 @@ void ISelfController::GetIdleTimeDetectionExtension(Kernel::HLERequestContext& c
rb.Push<u32>(idle_time_detection_extension);
}
+void ISelfController::GetAccumulatedSuspendedTickChangedEvent(Kernel::HLERequestContext& ctx) {
+ // The implementation of this function is fine as is, the reason we're labelling it as stubbed
+ // is because we're currently unsure when and where accumulated_suspended_tick_changed_event is
+ // actually signalled for the time being.
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushCopyObjects(accumulated_suspended_tick_changed_event.readable);
+}
+
AppletMessageQueue::AppletMessageQueue() {
auto& kernel = Core::System::GetInstance().Kernel();
- on_new_message = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Sticky,
+ on_new_message = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Manual,
"AMMessageQueue:OnMessageRecieved");
on_operation_mode_changed = Kernel::WritableEvent::CreateEventPair(
- kernel, Kernel::ResetType::OneShot, "AMMessageQueue:OperationModeChanged");
+ kernel, Kernel::ResetType::Automatic, "AMMessageQueue:OperationModeChanged");
}
AppletMessageQueue::~AppletMessageQueue() = default;
@@ -431,11 +533,20 @@ ICommonStateGetter::ICommonStateGetter(std::shared_ptr<AppletMessageQueue> msg_q
{50, nullptr, "IsVrModeEnabled"},
{51, nullptr, "SetVrModeEnabled"},
{52, nullptr, "SwitchLcdBacklight"},
+ {53, nullptr, "BeginVrModeEx"},
+ {54, nullptr, "EndVrModeEx"},
{55, nullptr, "IsInControllerFirmwareUpdateSection"},
{60, &ICommonStateGetter::GetDefaultDisplayResolution, "GetDefaultDisplayResolution"},
{61, &ICommonStateGetter::GetDefaultDisplayResolutionChangeEvent, "GetDefaultDisplayResolutionChangeEvent"},
{62, nullptr, "GetHdcpAuthenticationState"},
{63, nullptr, "GetHdcpAuthenticationStateChangeEvent"},
+ {64, nullptr, "SetTvPowerStateMatchingMode"},
+ {65, nullptr, "GetApplicationIdByContentActionName"},
+ {66, nullptr, "SetCpuBoostMode"},
+ {80, nullptr, "PerformSystemButtonPressingIfInFocus"},
+ {90, nullptr, "SetPerformanceConfigurationChangedNotification"},
+ {91, nullptr, "GetCurrentPerformanceConfiguration"},
+ {200, nullptr, "GetOperationModeSystemInfo"},
};
// clang-format on
@@ -744,6 +855,7 @@ void IStorageAccessor::Write(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERR_SIZE_OUT_OF_BOUNDS);
+ return;
}
std::memcpy(backing.buffer.data() + offset, data.data(), data.size());
@@ -766,6 +878,7 @@ void IStorageAccessor::Read(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERR_SIZE_OUT_OF_BOUNDS);
+ return;
}
ctx.WriteBuffer(backing.buffer.data() + offset, size);
@@ -788,30 +901,16 @@ ILibraryAppletCreator::ILibraryAppletCreator() : ServiceFramework("ILibraryApple
ILibraryAppletCreator::~ILibraryAppletCreator() = default;
-static std::shared_ptr<Applets::Applet> GetAppletFromId(AppletId id) {
- switch (id) {
- case AppletId::ProfileSelect:
- return std::make_shared<Applets::ProfileSelect>();
- case AppletId::SoftwareKeyboard:
- return std::make_shared<Applets::SoftwareKeyboard>();
- case AppletId::LibAppletOff:
- return std::make_shared<Applets::WebBrowser>();
- default:
- LOG_ERROR(Service_AM, "Unimplemented AppletId [{:08X}]! -- Falling back to stub!",
- static_cast<u32>(id));
- return std::make_shared<Applets::StubApplet>();
- }
-}
-
void ILibraryAppletCreator::CreateLibraryApplet(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto applet_id = rp.PopRaw<AppletId>();
+ 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);
- const auto applet = GetAppletFromId(applet_id);
+ const auto& applet_manager{Core::System::GetInstance().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));
@@ -847,19 +946,19 @@ void ILibraryAppletCreator::CreateTransferMemoryStorage(Kernel::HLERequestContex
rp.SetCurrentOffset(3);
const auto handle{rp.Pop<Kernel::Handle>()};
- const auto shared_mem =
- Core::System::GetInstance().CurrentProcess()->GetHandleTable().Get<Kernel::SharedMemory>(
+ const auto transfer_mem =
+ Core::System::GetInstance().CurrentProcess()->GetHandleTable().Get<Kernel::TransferMemory>(
handle);
- if (shared_mem == nullptr) {
+ if (transfer_mem == nullptr) {
LOG_ERROR(Service_AM, "shared_mem is a nullpr for handle={:08X}", handle);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultCode(-1));
return;
}
- const u8* mem_begin = shared_mem->GetPointer();
- const u8* mem_end = mem_begin + shared_mem->GetSize();
+ const u8* const mem_begin = transfer_mem->GetPointer();
+ const u8* const mem_end = mem_begin + transfer_mem->GetSize();
std::vector<u8> memory{mem_begin, mem_end};
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
@@ -875,6 +974,8 @@ IApplicationFunctions::IApplicationFunctions() : ServiceFramework("IApplicationF
{11, nullptr, "CreateApplicationAndPushAndRequestToStartForQuest"},
{12, nullptr, "CreateApplicationAndRequestToStart"},
{13, &IApplicationFunctions::CreateApplicationAndRequestToStartForQuest, "CreateApplicationAndRequestToStartForQuest"},
+ {14, nullptr, "CreateApplicationWithAttributeAndPushAndRequestToStartForQuest"},
+ {15, nullptr, "CreateApplicationWithAttributeAndRequestToStartForQuest"},
{20, &IApplicationFunctions::EnsureSaveData, "EnsureSaveData"},
{21, &IApplicationFunctions::GetDesiredLanguage, "GetDesiredLanguage"},
{22, &IApplicationFunctions::SetTerminateResult, "SetTerminateResult"},
@@ -1019,10 +1120,42 @@ void IApplicationFunctions::GetDesiredLanguage(Kernel::HLERequestContext& ctx) {
// TODO(bunnei): This should be configurable
LOG_DEBUG(Service_AM, "called");
+ // Get supported languages from NACP, if possible
+ // Default to 0 (all languages supported)
+ u32 supported_languages = 0;
+ FileSys::PatchManager pm{Core::System::GetInstance().CurrentProcess()->GetTitleID()};
+
+ const auto res = pm.GetControlMetadata();
+ if (res.first != nullptr) {
+ supported_languages = res.first->GetSupportedLanguages();
+ }
+
+ // Call IApplicationManagerInterface implementation.
+ auto& service_manager = Core::System::GetInstance().ServiceManager();
+ auto ns_am2 = service_manager.GetService<Service::NS::NS>("ns:am2");
+ auto app_man = ns_am2->GetApplicationManagerInterface();
+
+ // Get desired application language
+ const auto res_lang = app_man->GetApplicationDesiredLanguage(supported_languages);
+ if (res_lang.Failed()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(res_lang.Code());
+ return;
+ }
+
+ // Convert to settings language code.
+ const auto res_code = app_man->ConvertApplicationLanguageToLanguageCode(*res_lang);
+ if (res_code.Failed()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(res_code.Code());
+ return;
+ }
+
+ LOG_DEBUG(Service_AM, "got desired_language={:016X}", *res_code);
+
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
- rb.Push(
- static_cast<u64>(Service::Set::GetLanguageCodeFromIndex(Settings::values.language_index)));
+ rb.Push(*res_code);
}
void IApplicationFunctions::InitializeGamePlayRecording(Kernel::HLERequestContext& ctx) {
@@ -1148,6 +1281,7 @@ IGlobalStateController::IGlobalStateController() : ServiceFramework("IGlobalStat
{2, nullptr, "StartSleepSequence"},
{3, nullptr, "StartShutdownSequence"},
{4, nullptr, "StartRebootSequence"},
+ {9, nullptr, "IsAutoPowerDownRequested"},
{10, nullptr, "LoadAndApplyIdlePolicySettings"},
{11, nullptr, "NotifyCecSettingsChanged"},
{12, nullptr, "SetDefaultHomeButtonLongPressTime"},
diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h
index b6113cfdd..1fa069e56 100644
--- a/src/core/hle/service/am/am.h
+++ b/src/core/hle/service/am/am.h
@@ -4,6 +4,7 @@
#pragma once
+#include <chrono>
#include <memory>
#include <queue>
#include "core/hle/kernel/writable_event.h"
@@ -81,8 +82,21 @@ private:
void SetExpectedMasterVolume(Kernel::HLERequestContext& ctx);
void GetMainAppletExpectedMasterVolume(Kernel::HLERequestContext& ctx);
void GetLibraryAppletExpectedMasterVolume(Kernel::HLERequestContext& ctx);
+ void ChangeMainAppletMasterVolume(Kernel::HLERequestContext& ctx);
+ void SetTransparentAudioRate(Kernel::HLERequestContext& ctx);
- u32 volume{100};
+ static constexpr float min_allowed_volume = 0.0f;
+ static constexpr float max_allowed_volume = 1.0f;
+
+ float main_applet_volume{0.25f};
+ float library_applet_volume{max_allowed_volume};
+ float transparent_volume_rate{min_allowed_volume};
+
+ // Volume transition fade time in nanoseconds.
+ // e.g. If the main applet volume was 0% and was changed to 50%
+ // with a fade of 50ns, then over the course of 50ns,
+ // the volume will gradually fade up to 50%
+ std::chrono::nanoseconds fade_time_ns{0};
};
class IDisplayController final : public ServiceFramework<IDisplayController> {
@@ -103,24 +117,30 @@ public:
~ISelfController() override;
private:
- void SetFocusHandlingMode(Kernel::HLERequestContext& ctx);
- void SetRestartMessageEnabled(Kernel::HLERequestContext& ctx);
- void SetPerformanceModeChangedNotification(Kernel::HLERequestContext& ctx);
- void SetOperationModeChangedNotification(Kernel::HLERequestContext& ctx);
- void SetOutOfFocusSuspendingEnabled(Kernel::HLERequestContext& ctx);
void LockExit(Kernel::HLERequestContext& ctx);
void UnlockExit(Kernel::HLERequestContext& ctx);
+ void EnterFatalSection(Kernel::HLERequestContext& ctx);
+ void LeaveFatalSection(Kernel::HLERequestContext& ctx);
void GetLibraryAppletLaunchableEvent(Kernel::HLERequestContext& ctx);
+ void SetScreenShotPermission(Kernel::HLERequestContext& ctx);
+ void SetOperationModeChangedNotification(Kernel::HLERequestContext& ctx);
+ void SetPerformanceModeChangedNotification(Kernel::HLERequestContext& ctx);
+ void SetFocusHandlingMode(Kernel::HLERequestContext& ctx);
+ void SetRestartMessageEnabled(Kernel::HLERequestContext& ctx);
+ void SetOutOfFocusSuspendingEnabled(Kernel::HLERequestContext& ctx);
void SetScreenShotImageOrientation(Kernel::HLERequestContext& ctx);
void CreateManagedDisplayLayer(Kernel::HLERequestContext& ctx);
- void SetScreenShotPermission(Kernel::HLERequestContext& ctx);
void SetHandlesRequestToDisplay(Kernel::HLERequestContext& ctx);
void SetIdleTimeDetectionExtension(Kernel::HLERequestContext& ctx);
void GetIdleTimeDetectionExtension(Kernel::HLERequestContext& ctx);
+ void GetAccumulatedSuspendedTickChangedEvent(Kernel::HLERequestContext& ctx);
std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
Kernel::EventPair launchable_event;
+ Kernel::EventPair accumulated_suspended_tick_changed_event;
+
u32 idle_time_detection_extension = 0;
+ u64 num_fatal_sections_entered = 0;
};
class ICommonStateGetter final : public ServiceFramework<ICommonStateGetter> {
diff --git a/src/core/hle/service/am/applet_ae.cpp b/src/core/hle/service/am/applet_ae.cpp
index b888f861d..488add8e7 100644
--- a/src/core/hle/service/am/applet_ae.cpp
+++ b/src/core/hle/service/am/applet_ae.cpp
@@ -16,6 +16,7 @@ public:
std::shared_ptr<AppletMessageQueue> msg_queue)
: ServiceFramework("ILibraryAppletProxy"), nvflinger(std::move(nvflinger)),
msg_queue(std::move(msg_queue)) {
+ // clang-format off
static const FunctionInfo functions[] = {
{0, &ILibraryAppletProxy::GetCommonStateGetter, "GetCommonStateGetter"},
{1, &ILibraryAppletProxy::GetSelfController, "GetSelfController"},
@@ -25,8 +26,11 @@ public:
{10, &ILibraryAppletProxy::GetProcessWindingController, "GetProcessWindingController"},
{11, &ILibraryAppletProxy::GetLibraryAppletCreator, "GetLibraryAppletCreator"},
{20, &ILibraryAppletProxy::GetApplicationFunctions, "GetApplicationFunctions"},
+ {21, nullptr, "GetAppletCommonFunctions"},
{1000, &ILibraryAppletProxy::GetDebugFunctions, "GetDebugFunctions"},
};
+ // clang-format on
+
RegisterHandlers(functions);
}
@@ -113,6 +117,7 @@ public:
std::shared_ptr<AppletMessageQueue> msg_queue)
: ServiceFramework("ISystemAppletProxy"), nvflinger(std::move(nvflinger)),
msg_queue(std::move(msg_queue)) {
+ // clang-format off
static const FunctionInfo functions[] = {
{0, &ISystemAppletProxy::GetCommonStateGetter, "GetCommonStateGetter"},
{1, &ISystemAppletProxy::GetSelfController, "GetSelfController"},
@@ -124,8 +129,11 @@ public:
{20, &ISystemAppletProxy::GetHomeMenuFunctions, "GetHomeMenuFunctions"},
{21, &ISystemAppletProxy::GetGlobalStateController, "GetGlobalStateController"},
{22, &ISystemAppletProxy::GetApplicationCreator, "GetApplicationCreator"},
+ {23, nullptr, "GetAppletCommonFunctions"},
{1000, &ISystemAppletProxy::GetDebugFunctions, "GetDebugFunctions"},
};
+ // clang-format on
+
RegisterHandlers(functions);
}
diff --git a/src/core/hle/service/am/applets/applets.cpp b/src/core/hle/service/am/applets/applets.cpp
index a6064c63f..14fa92318 100644
--- a/src/core/hle/service/am/applets/applets.cpp
+++ b/src/core/hle/service/am/applets/applets.cpp
@@ -5,22 +5,32 @@
#include <cstring>
#include "common/assert.h"
#include "core/core.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/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/error.h"
+#include "core/hle/service/am/applets/general_backend.h"
+#include "core/hle/service/am/applets/profile_select.h"
+#include "core/hle/service/am/applets/software_keyboard.h"
+#include "core/hle/service/am/applets/web_browser.h"
namespace Service::AM::Applets {
AppletDataBroker::AppletDataBroker() {
auto& kernel = Core::System::GetInstance().Kernel();
state_changed_event = Kernel::WritableEvent::CreateEventPair(
- kernel, Kernel::ResetType::Sticky, "ILibraryAppletAccessor:StateChangedEvent");
+ kernel, Kernel::ResetType::Manual, "ILibraryAppletAccessor:StateChangedEvent");
pop_out_data_event = Kernel::WritableEvent::CreateEventPair(
- kernel, Kernel::ResetType::Sticky, "ILibraryAppletAccessor:PopDataOutEvent");
+ kernel, Kernel::ResetType::Manual, "ILibraryAppletAccessor:PopDataOutEvent");
pop_interactive_out_data_event = Kernel::WritableEvent::CreateEventPair(
- kernel, Kernel::ResetType::Sticky, "ILibraryAppletAccessor:PopInteractiveDataOutEvent");
+ kernel, Kernel::ResetType::Manual, "ILibraryAppletAccessor:PopInteractiveDataOutEvent");
}
AppletDataBroker::~AppletDataBroker() = default;
@@ -111,4 +121,91 @@ void Applet::Initialize() {
initialized = true;
}
+AppletFrontendSet::AppletFrontendSet() = default;
+
+AppletFrontendSet::AppletFrontendSet(ErrorApplet error, PhotoViewer photo_viewer,
+ ProfileSelect profile_select,
+ SoftwareKeyboard software_keyboard, WebBrowser web_browser)
+ : 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)} {}
+
+AppletFrontendSet::~AppletFrontendSet() = default;
+
+AppletFrontendSet::AppletFrontendSet(AppletFrontendSet&&) noexcept = default;
+
+AppletFrontendSet& AppletFrontendSet::operator=(AppletFrontendSet&&) noexcept = default;
+
+AppletManager::AppletManager() = default;
+
+AppletManager::~AppletManager() = default;
+
+void AppletManager::SetAppletFrontendSet(AppletFrontendSet set) {
+ if (set.error != nullptr)
+ frontend.error = std::move(set.error);
+ if (set.photo_viewer != nullptr)
+ frontend.photo_viewer = std::move(set.photo_viewer);
+ if (set.profile_select != nullptr)
+ frontend.profile_select = std::move(set.profile_select);
+ if (set.software_keyboard != nullptr)
+ frontend.software_keyboard = std::move(set.software_keyboard);
+ if (set.web_browser != nullptr)
+ frontend.web_browser = std::move(set.web_browser);
+}
+
+void AppletManager::SetDefaultAppletFrontendSet() {
+ frontend.error = std::make_unique<Core::Frontend::DefaultErrorApplet>();
+ frontend.photo_viewer = std::make_unique<Core::Frontend::DefaultPhotoViewerApplet>();
+ frontend.profile_select = std::make_unique<Core::Frontend::DefaultProfileSelectApplet>();
+ frontend.software_keyboard = std::make_unique<Core::Frontend::DefaultSoftwareKeyboardApplet>();
+ frontend.web_browser = std::make_unique<Core::Frontend::DefaultWebBrowserApplet>();
+}
+
+void AppletManager::SetDefaultAppletsIfMissing() {
+ if (frontend.error == nullptr) {
+ frontend.error = std::make_unique<Core::Frontend::DefaultErrorApplet>();
+ }
+
+ if (frontend.photo_viewer == nullptr) {
+ frontend.photo_viewer = std::make_unique<Core::Frontend::DefaultPhotoViewerApplet>();
+ }
+
+ if (frontend.profile_select == nullptr) {
+ frontend.profile_select = std::make_unique<Core::Frontend::DefaultProfileSelectApplet>();
+ }
+
+ if (frontend.software_keyboard == nullptr) {
+ frontend.software_keyboard =
+ std::make_unique<Core::Frontend::DefaultSoftwareKeyboardApplet>();
+ }
+
+ if (frontend.web_browser == nullptr) {
+ frontend.web_browser = std::make_unique<Core::Frontend::DefaultWebBrowserApplet>();
+ }
+}
+
+void AppletManager::ClearAll() {
+ frontend = {};
+}
+
+std::shared_ptr<Applet> AppletManager::GetApplet(AppletId id) const {
+ switch (id) {
+ case AppletId::Error:
+ return std::make_shared<Error>(*frontend.error);
+ case AppletId::ProfileSelect:
+ return std::make_shared<ProfileSelect>(*frontend.profile_select);
+ case AppletId::SoftwareKeyboard:
+ return std::make_shared<SoftwareKeyboard>(*frontend.software_keyboard);
+ case AppletId::PhotoViewer:
+ return std::make_shared<PhotoViewer>(*frontend.photo_viewer);
+ case AppletId::LibAppletOff:
+ return std::make_shared<WebBrowser>(*frontend.web_browser);
+ default:
+ UNIMPLEMENTED_MSG(
+ "No backend implementation exists for applet_id={:02X}! Falling back to stub applet.",
+ static_cast<u8>(id));
+ return std::make_shared<StubApplet>();
+ }
+}
+
} // namespace Service::AM::Applets
diff --git a/src/core/hle/service/am/applets/applets.h b/src/core/hle/service/am/applets/applets.h
index 37424c379..b46e10a4a 100644
--- a/src/core/hle/service/am/applets/applets.h
+++ b/src/core/hle/service/am/applets/applets.h
@@ -12,12 +12,43 @@
union ResultCode;
+namespace Core::Frontend {
+class ErrorApplet;
+class PhotoViewerApplet;
+class ProfileSelectApplet;
+class SoftwareKeyboardApplet;
+class WebBrowserApplet;
+} // namespace Core::Frontend
+
namespace Service::AM {
class IStorage;
namespace Applets {
+enum class AppletId : u32 {
+ OverlayDisplay = 0x02,
+ QLaunch = 0x03,
+ Starter = 0x04,
+ Auth = 0x0A,
+ Cabinet = 0x0B,
+ Controller = 0x0C,
+ DataErase = 0x0D,
+ Error = 0x0E,
+ NetConnect = 0x0F,
+ ProfileSelect = 0x10,
+ SoftwareKeyboard = 0x11,
+ MiiEdit = 0x12,
+ LibAppletWeb = 0x13,
+ LibAppletShop = 0x14,
+ PhotoViewer = 0x15,
+ Settings = 0x16,
+ LibAppletOff = 0x17,
+ LibAppletWhitelisted = 0x18,
+ LibAppletAuth = 0x19,
+ MyPage = 0x1A,
+};
+
class AppletDataBroker final {
public:
AppletDataBroker();
@@ -105,5 +136,46 @@ protected:
bool initialized = false;
};
+struct AppletFrontendSet {
+ using ErrorApplet = std::unique_ptr<Core::Frontend::ErrorApplet>;
+ 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>;
+
+ AppletFrontendSet();
+ AppletFrontendSet(ErrorApplet error, PhotoViewer photo_viewer, ProfileSelect profile_select,
+ SoftwareKeyboard software_keyboard, WebBrowser web_browser);
+ ~AppletFrontendSet();
+
+ AppletFrontendSet(const AppletFrontendSet&) = delete;
+ AppletFrontendSet& operator=(const AppletFrontendSet&) = delete;
+
+ AppletFrontendSet(AppletFrontendSet&&) noexcept;
+ AppletFrontendSet& operator=(AppletFrontendSet&&) noexcept;
+
+ ErrorApplet error;
+ PhotoViewer photo_viewer;
+ ProfileSelect profile_select;
+ SoftwareKeyboard software_keyboard;
+ WebBrowser web_browser;
+};
+
+class AppletManager {
+public:
+ AppletManager();
+ ~AppletManager();
+
+ void SetAppletFrontendSet(AppletFrontendSet set);
+ void SetDefaultAppletFrontendSet();
+ void SetDefaultAppletsIfMissing();
+ void ClearAll();
+
+ std::shared_ptr<Applet> GetApplet(AppletId id) const;
+
+private:
+ AppletFrontendSet frontend;
+};
+
} // namespace Applets
} // namespace Service::AM
diff --git a/src/core/hle/service/am/applets/error.cpp b/src/core/hle/service/am/applets/error.cpp
new file mode 100644
index 000000000..04774bedc
--- /dev/null
+++ b/src/core/hle/service/am/applets/error.cpp
@@ -0,0 +1,182 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <array>
+#include <cstring>
+#include "common/assert.h"
+#include "common/logging/log.h"
+#include "common/string_util.h"
+#include "core/core.h"
+#include "core/frontend/applets/error.h"
+#include "core/hle/service/am/am.h"
+#include "core/hle/service/am/applets/error.h"
+
+namespace Service::AM::Applets {
+
+#pragma pack(push, 4)
+struct ShowError {
+ u8 mode;
+ bool jump;
+ INSERT_PADDING_BYTES(4);
+ bool use_64bit_error_code;
+ INSERT_PADDING_BYTES(1);
+ u64 error_code_64;
+ u32 error_code_32;
+};
+static_assert(sizeof(ShowError) == 0x14, "ShowError has incorrect size.");
+#pragma pack(pop)
+
+struct ShowErrorRecord {
+ u8 mode;
+ bool jump;
+ INSERT_PADDING_BYTES(6);
+ u64 error_code_64;
+ u64 posix_time;
+};
+static_assert(sizeof(ShowErrorRecord) == 0x18, "ShowErrorRecord has incorrect size.");
+
+struct SystemErrorArg {
+ u8 mode;
+ bool jump;
+ INSERT_PADDING_BYTES(6);
+ u64 error_code_64;
+ std::array<char, 8> language_code;
+ std::array<char, 0x800> main_text;
+ std::array<char, 0x800> detail_text;
+};
+static_assert(sizeof(SystemErrorArg) == 0x1018, "SystemErrorArg has incorrect size.");
+
+struct ApplicationErrorArg {
+ u8 mode;
+ bool jump;
+ INSERT_PADDING_BYTES(6);
+ u32 error_code;
+ std::array<char, 8> language_code;
+ std::array<char, 0x800> main_text;
+ std::array<char, 0x800> detail_text;
+};
+static_assert(sizeof(ApplicationErrorArg) == 0x1014, "ApplicationErrorArg has incorrect size.");
+
+union Error::ErrorArguments {
+ ShowError error;
+ ShowErrorRecord error_record;
+ SystemErrorArg system_error;
+ ApplicationErrorArg application_error;
+};
+
+namespace {
+template <typename T>
+void CopyArgumentData(const std::vector<u8>& data, T& variable) {
+ ASSERT(data.size() >= sizeof(T));
+ std::memcpy(&variable, data.data(), sizeof(T));
+}
+
+ResultCode Decode64BitError(u64 error) {
+ const auto description = (error >> 32) & 0x1FFF;
+ auto module = error & 0x3FF;
+ if (module >= 2000)
+ module -= 2000;
+ module &= 0x1FF;
+ return {static_cast<ErrorModule>(module), static_cast<u32>(description)};
+}
+
+} // Anonymous namespace
+
+Error::Error(const Core::Frontend::ErrorApplet& frontend) : frontend(frontend) {}
+
+Error::~Error() = default;
+
+void Error::Initialize() {
+ Applet::Initialize();
+ args = std::make_unique<ErrorArguments>();
+ complete = false;
+
+ const auto storage = broker.PopNormalDataToApplet();
+ ASSERT(storage != nullptr);
+ const auto data = storage->GetData();
+
+ ASSERT(!data.empty());
+ std::memcpy(&mode, data.data(), sizeof(ErrorAppletMode));
+
+ switch (mode) {
+ case ErrorAppletMode::ShowError:
+ CopyArgumentData(data, args->error);
+ if (args->error.use_64bit_error_code) {
+ error_code = Decode64BitError(args->error.error_code_64);
+ } else {
+ error_code = ResultCode(args->error.error_code_32);
+ }
+ break;
+ case ErrorAppletMode::ShowSystemError:
+ CopyArgumentData(data, args->system_error);
+ error_code = ResultCode(Decode64BitError(args->system_error.error_code_64));
+ break;
+ case ErrorAppletMode::ShowApplicationError:
+ CopyArgumentData(data, args->application_error);
+ error_code = ResultCode(args->application_error.error_code);
+ break;
+ case ErrorAppletMode::ShowErrorRecord:
+ CopyArgumentData(data, args->error_record);
+ error_code = Decode64BitError(args->error_record.error_code_64);
+ break;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented LibAppletError mode={:02X}!", static_cast<u8>(mode));
+ }
+}
+
+bool Error::TransactionComplete() const {
+ return complete;
+}
+
+ResultCode Error::GetStatus() const {
+ return RESULT_SUCCESS;
+}
+
+void Error::ExecuteInteractive() {
+ UNREACHABLE_MSG("Unexpected interactive applet data!");
+}
+
+void Error::Execute() {
+ if (complete) {
+ return;
+ }
+
+ const auto callback = [this] { DisplayCompleted(); };
+
+ switch (mode) {
+ case ErrorAppletMode::ShowError:
+ frontend.ShowError(error_code, callback);
+ break;
+ case ErrorAppletMode::ShowSystemError:
+ case ErrorAppletMode::ShowApplicationError: {
+ const auto system = mode == ErrorAppletMode::ShowSystemError;
+ const auto& main_text =
+ system ? args->system_error.main_text : args->application_error.main_text;
+ const auto& detail_text =
+ system ? args->system_error.detail_text : args->application_error.detail_text;
+
+ frontend.ShowCustomErrorText(
+ error_code,
+ Common::StringFromFixedZeroTerminatedBuffer(main_text.data(), main_text.size()),
+ Common::StringFromFixedZeroTerminatedBuffer(detail_text.data(), detail_text.size()),
+ callback);
+ break;
+ }
+ case ErrorAppletMode::ShowErrorRecord:
+ frontend.ShowErrorWithTimestamp(
+ error_code, std::chrono::seconds{args->error_record.posix_time}, callback);
+ break;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented LibAppletError mode={:02X}!", static_cast<u8>(mode));
+ DisplayCompleted();
+ }
+}
+
+void Error::DisplayCompleted() {
+ complete = true;
+ broker.PushNormalDataFromApplet(IStorage{{}});
+ broker.SignalStateChanged();
+}
+
+} // namespace Service::AM::Applets
diff --git a/src/core/hle/service/am/applets/error.h b/src/core/hle/service/am/applets/error.h
new file mode 100644
index 000000000..a3590d181
--- /dev/null
+++ b/src/core/hle/service/am/applets/error.h
@@ -0,0 +1,47 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/result.h"
+#include "core/hle/service/am/applets/applets.h"
+
+namespace Service::AM::Applets {
+
+enum class ErrorAppletMode : u8 {
+ ShowError = 0,
+ ShowSystemError = 1,
+ ShowApplicationError = 2,
+ ShowEula = 3,
+ ShowErrorPctl = 4,
+ ShowErrorRecord = 5,
+ ShowUpdateEula = 8,
+};
+
+class Error final : public Applet {
+public:
+ explicit Error(const Core::Frontend::ErrorApplet& frontend);
+ ~Error() override;
+
+ void Initialize() override;
+
+ bool TransactionComplete() const override;
+ ResultCode GetStatus() const override;
+ void ExecuteInteractive() override;
+ void Execute() override;
+
+ void DisplayCompleted();
+
+private:
+ union ErrorArguments;
+
+ const Core::Frontend::ErrorApplet& frontend;
+ ResultCode error_code = RESULT_SUCCESS;
+ ErrorAppletMode mode = ErrorAppletMode::ShowError;
+ std::unique_ptr<ErrorArguments> args;
+
+ bool complete = false;
+};
+
+} // namespace Service::AM::Applets
diff --git a/src/core/hle/service/am/applets/stub_applet.cpp b/src/core/hle/service/am/applets/general_backend.cpp
index ed166b87d..76fc8906d 100644
--- a/src/core/hle/service/am/applets/stub_applet.cpp
+++ b/src/core/hle/service/am/applets/general_backend.cpp
@@ -2,34 +2,87 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include <string>
+#include <string_view>
+#include "common/assert.h"
#include "common/hex_util.h"
#include "common/logging/log.h"
+#include "core/core.h"
+#include "core/frontend/applets/general_frontend.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/stub_applet.h"
+#include "core/hle/service/am/applets/general_backend.h"
namespace Service::AM::Applets {
-static void LogCurrentStorage(AppletDataBroker& broker, std::string prefix) {
+static void LogCurrentStorage(AppletDataBroker& broker, std::string_view prefix) {
std::unique_ptr<IStorage> storage = broker.PopNormalDataToApplet();
for (; storage != nullptr; storage = broker.PopNormalDataToApplet()) {
const auto data = storage->GetData();
LOG_INFO(Service_AM,
- "called (STUBBED), during {} recieved normal data with size={:08X}, data={}",
- prefix, data.size(), Common::HexVectorToString(data));
+ "called (STUBBED), during {} received normal data with size={:08X}, data={}",
+ prefix, data.size(), Common::HexToString(data));
}
storage = broker.PopInteractiveDataToApplet();
for (; storage != nullptr; storage = broker.PopInteractiveDataToApplet()) {
const auto data = storage->GetData();
LOG_INFO(Service_AM,
- "called (STUBBED), during {} recieved interactive data with size={:08X}, data={}",
- prefix, data.size(), Common::HexVectorToString(data));
+ "called (STUBBED), during {} received interactive data with size={:08X}, data={}",
+ prefix, data.size(), Common::HexToString(data));
}
}
+PhotoViewer::PhotoViewer(const Core::Frontend::PhotoViewerApplet& frontend) : frontend(frontend) {}
+
+PhotoViewer::~PhotoViewer() = default;
+
+void PhotoViewer::Initialize() {
+ Applet::Initialize();
+ complete = false;
+
+ const auto storage = broker.PopNormalDataToApplet();
+ ASSERT(storage != nullptr);
+ const auto data = storage->GetData();
+ ASSERT(!data.empty());
+ mode = static_cast<PhotoViewerAppletMode>(data[0]);
+}
+
+bool PhotoViewer::TransactionComplete() const {
+ return complete;
+}
+
+ResultCode PhotoViewer::GetStatus() const {
+ return RESULT_SUCCESS;
+}
+
+void PhotoViewer::ExecuteInteractive() {
+ UNREACHABLE_MSG("Unexpected interactive applet data.");
+}
+
+void PhotoViewer::Execute() {
+ if (complete)
+ return;
+
+ const auto callback = [this] { ViewFinished(); };
+ switch (mode) {
+ case PhotoViewerAppletMode::CurrentApp:
+ frontend.ShowPhotosForApplication(Core::CurrentProcess()->GetTitleID(), callback);
+ break;
+ case PhotoViewerAppletMode::AllApps:
+ frontend.ShowAllPhotos(callback);
+ break;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented PhotoViewer applet mode={:02X}!", static_cast<u8>(mode));
+ }
+}
+
+void PhotoViewer::ViewFinished() {
+ broker.PushNormalDataFromApplet(IStorage{{}});
+ broker.SignalStateChanged();
+}
+
StubApplet::StubApplet() = default;
StubApplet::~StubApplet() = default;
@@ -67,4 +120,5 @@ void StubApplet::Execute() {
broker.PushInteractiveDataFromApplet(IStorage{std::vector<u8>(0x1000)});
broker.SignalStateChanged();
}
+
} // namespace Service::AM::Applets
diff --git a/src/core/hle/service/am/applets/general_backend.h b/src/core/hle/service/am/applets/general_backend.h
new file mode 100644
index 000000000..2dd255d7c
--- /dev/null
+++ b/src/core/hle/service/am/applets/general_backend.h
@@ -0,0 +1,48 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/service/am/applets/applets.h"
+
+namespace Service::AM::Applets {
+
+enum class PhotoViewerAppletMode : u8 {
+ CurrentApp = 0,
+ AllApps = 1,
+};
+
+class PhotoViewer final : public Applet {
+public:
+ explicit PhotoViewer(const Core::Frontend::PhotoViewerApplet& frontend);
+ ~PhotoViewer() override;
+
+ void Initialize() override;
+ bool TransactionComplete() const override;
+ ResultCode GetStatus() const override;
+ void ExecuteInteractive() override;
+ void Execute() override;
+
+ void ViewFinished();
+
+private:
+ const Core::Frontend::PhotoViewerApplet& frontend;
+ bool complete = false;
+ PhotoViewerAppletMode mode = PhotoViewerAppletMode::CurrentApp;
+};
+
+class StubApplet final : public Applet {
+public:
+ StubApplet();
+ ~StubApplet() override;
+
+ void Initialize() override;
+
+ bool TransactionComplete() const override;
+ ResultCode GetStatus() const override;
+ void ExecuteInteractive() override;
+ void Execute() override;
+};
+
+} // namespace Service::AM::Applets
diff --git a/src/core/hle/service/am/applets/profile_select.cpp b/src/core/hle/service/am/applets/profile_select.cpp
index 14e2a1fee..57b5419e8 100644
--- a/src/core/hle/service/am/applets/profile_select.cpp
+++ b/src/core/hle/service/am/applets/profile_select.cpp
@@ -15,7 +15,9 @@ namespace Service::AM::Applets {
constexpr ResultCode ERR_USER_CANCELLED_SELECTION{ErrorModule::Account, 1};
-ProfileSelect::ProfileSelect() = default;
+ProfileSelect::ProfileSelect(const Core::Frontend::ProfileSelectApplet& frontend)
+ : frontend(frontend) {}
+
ProfileSelect::~ProfileSelect() = default;
void ProfileSelect::Initialize() {
@@ -51,21 +53,19 @@ void ProfileSelect::Execute() {
return;
}
- const auto& frontend{Core::System::GetInstance().GetProfileSelector()};
-
- frontend.SelectProfile([this](std::optional<Account::UUID> uuid) { SelectionComplete(uuid); });
+ frontend.SelectProfile([this](std::optional<Common::UUID> uuid) { SelectionComplete(uuid); });
}
-void ProfileSelect::SelectionComplete(std::optional<Account::UUID> uuid) {
+void ProfileSelect::SelectionComplete(std::optional<Common::UUID> uuid) {
UserSelectionOutput output{};
- if (uuid.has_value() && uuid->uuid != Account::INVALID_UUID) {
+ if (uuid.has_value() && uuid->uuid != Common::INVALID_UUID) {
output.result = 0;
output.uuid_selected = uuid->uuid;
} else {
status = ERR_USER_CANCELLED_SELECTION;
output.result = ERR_USER_CANCELLED_SELECTION.raw;
- output.uuid_selected = Account::INVALID_UUID;
+ output.uuid_selected = Common::INVALID_UUID;
}
final_data = std::vector<u8>(sizeof(UserSelectionOutput));
diff --git a/src/core/hle/service/am/applets/profile_select.h b/src/core/hle/service/am/applets/profile_select.h
index 787485f22..563cd744a 100644
--- a/src/core/hle/service/am/applets/profile_select.h
+++ b/src/core/hle/service/am/applets/profile_select.h
@@ -7,7 +7,8 @@
#include <vector>
#include "common/common_funcs.h"
-#include "core/hle/service/acc/profile_manager.h"
+#include "common/uuid.h"
+#include "core/hle/result.h"
#include "core/hle/service/am/applets/applets.h"
namespace Service::AM::Applets {
@@ -28,7 +29,7 @@ static_assert(sizeof(UserSelectionOutput) == 0x18, "UserSelectionOutput has inco
class ProfileSelect final : public Applet {
public:
- ProfileSelect();
+ explicit ProfileSelect(const Core::Frontend::ProfileSelectApplet& frontend);
~ProfileSelect() override;
void Initialize() override;
@@ -38,9 +39,11 @@ public:
void ExecuteInteractive() override;
void Execute() override;
- void SelectionComplete(std::optional<Account::UUID> uuid);
+ void SelectionComplete(std::optional<Common::UUID> uuid);
private:
+ const Core::Frontend::ProfileSelectApplet& frontend;
+
UserSelectionConfig config;
bool complete = false;
ResultCode status = RESULT_SUCCESS;
diff --git a/src/core/hle/service/am/applets/software_keyboard.cpp b/src/core/hle/service/am/applets/software_keyboard.cpp
index 8c5bd6059..e197990f7 100644
--- a/src/core/hle/service/am/applets/software_keyboard.cpp
+++ b/src/core/hle/service/am/applets/software_keyboard.cpp
@@ -39,7 +39,8 @@ static Core::Frontend::SoftwareKeyboardParameters ConvertToFrontendParameters(
return params;
}
-SoftwareKeyboard::SoftwareKeyboard() = default;
+SoftwareKeyboard::SoftwareKeyboard(const Core::Frontend::SoftwareKeyboardApplet& frontend)
+ : frontend(frontend) {}
SoftwareKeyboard::~SoftwareKeyboard() = default;
@@ -90,8 +91,6 @@ void SoftwareKeyboard::ExecuteInteractive() {
if (status == INTERACTIVE_STATUS_OK) {
complete = true;
} else {
- const auto& frontend{Core::System::GetInstance().GetSoftwareKeyboard()};
-
std::array<char16_t, SWKBD_OUTPUT_INTERACTIVE_BUFFER_SIZE / 2 - 2> string;
std::memcpy(string.data(), data.data() + 4, string.size() * 2);
frontend.SendTextCheckDialog(
@@ -106,8 +105,6 @@ void SoftwareKeyboard::Execute() {
return;
}
- const auto& frontend{Core::System::GetInstance().GetSoftwareKeyboard()};
-
const auto parameters = ConvertToFrontendParameters(config, initial_text);
frontend.RequestText([this](std::optional<std::u16string> text) { WriteText(text); },
diff --git a/src/core/hle/service/am/applets/software_keyboard.h b/src/core/hle/service/am/applets/software_keyboard.h
index b93a30d28..0fbc43e51 100644
--- a/src/core/hle/service/am/applets/software_keyboard.h
+++ b/src/core/hle/service/am/applets/software_keyboard.h
@@ -55,7 +55,7 @@ static_assert(sizeof(KeyboardConfig) == 0x3E0, "KeyboardConfig has incorrect siz
class SoftwareKeyboard final : public Applet {
public:
- SoftwareKeyboard();
+ explicit SoftwareKeyboard(const Core::Frontend::SoftwareKeyboardApplet& frontend);
~SoftwareKeyboard() override;
void Initialize() override;
@@ -68,6 +68,8 @@ public:
void WriteText(std::optional<std::u16string> text);
private:
+ const Core::Frontend::SoftwareKeyboardApplet& frontend;
+
KeyboardConfig config;
std::u16string initial_text;
bool complete = false;
diff --git a/src/core/hle/service/am/applets/stub_applet.h b/src/core/hle/service/am/applets/stub_applet.h
deleted file mode 100644
index 7d8dc968d..000000000
--- a/src/core/hle/service/am/applets/stub_applet.h
+++ /dev/null
@@ -1,24 +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/service/am/applets/applets.h"
-
-namespace Service::AM::Applets {
-
-class StubApplet final : public Applet {
-public:
- StubApplet();
- ~StubApplet() override;
-
- void Initialize() override;
-
- bool TransactionComplete() const override;
- ResultCode GetStatus() const override;
- void ExecuteInteractive() override;
- void Execute() override;
-};
-
-} // 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 9b0aa7f5f..7878f5136 100644
--- a/src/core/hle/service/am/applets/web_browser.cpp
+++ b/src/core/hle/service/am/applets/web_browser.cpp
@@ -86,7 +86,7 @@ static FileSys::VirtualFile GetManualRomFS() {
if (loader.ReadManualRomFS(out) == Loader::ResultStatus::Success)
return out;
- const auto& installed{FileSystem::GetUnionContents()};
+ const auto& installed{Core::System::GetInstance().GetContentProvider()};
const auto res = installed.GetEntry(Core::System::GetInstance().CurrentProcess()->GetTitleID(),
FileSys::ContentRecordType::Manual);
@@ -95,7 +95,7 @@ static FileSys::VirtualFile GetManualRomFS() {
return nullptr;
}
-WebBrowser::WebBrowser() = default;
+WebBrowser::WebBrowser(Core::Frontend::WebBrowserApplet& frontend) : frontend(frontend) {}
WebBrowser::~WebBrowser() = default;
@@ -152,8 +152,6 @@ void WebBrowser::Execute() {
return;
}
- auto& frontend{Core::System::GetInstance().GetWebBrowser()};
-
frontend.OpenPage(filename, [this] { UnpackRomFS(); }, [this] { Finalize(); });
}
diff --git a/src/core/hle/service/am/applets/web_browser.h b/src/core/hle/service/am/applets/web_browser.h
index b9e228fac..7e0f34c7d 100644
--- a/src/core/hle/service/am/applets/web_browser.h
+++ b/src/core/hle/service/am/applets/web_browser.h
@@ -12,7 +12,7 @@ namespace Service::AM::Applets {
class WebBrowser final : public Applet {
public:
- WebBrowser();
+ WebBrowser(Core::Frontend::WebBrowserApplet& frontend);
~WebBrowser() override;
void Initialize() override;
@@ -32,6 +32,8 @@ public:
void Finalize();
private:
+ Core::Frontend::WebBrowserApplet& frontend;
+
bool complete = false;
bool unpacked = false;
ResultCode status = RESULT_SUCCESS;
diff --git a/src/core/hle/service/aoc/aoc_u.cpp b/src/core/hle/service/aoc/aoc_u.cpp
index b506bc3dd..d3e97776b 100644
--- a/src/core/hle/service/aoc/aoc_u.cpp
+++ b/src/core/hle/service/aoc/aoc_u.cpp
@@ -9,7 +9,6 @@
#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/partition_filesystem.h"
#include "core/file_sys/patch_manager.h"
#include "core/file_sys/registered_cache.h"
#include "core/hle/ipc_helpers.h"
@@ -18,7 +17,6 @@
#include "core/hle/kernel/readable_event.h"
#include "core/hle/kernel/writable_event.h"
#include "core/hle/service/aoc/aoc_u.h"
-#include "core/hle/service/filesystem/filesystem.h"
#include "core/loader/loader.h"
#include "core/settings.h"
@@ -33,11 +31,11 @@ static bool CheckAOCTitleIDMatchesBase(u64 title_id, u64 base) {
static std::vector<u64> AccumulateAOCTitleIDs() {
std::vector<u64> add_on_content;
- const auto rcu = FileSystem::GetUnionContents();
+ const auto& rcu = Core::System::GetInstance().GetContentProvider();
const auto list =
rcu.ListEntriesFilter(FileSys::TitleType::AOC, FileSys::ContentRecordType::Data);
std::transform(list.begin(), list.end(), std::back_inserter(add_on_content),
- [](const FileSys::RegisteredCacheEntry& rce) { return rce.title_id; });
+ [](const FileSys::ContentProviderEntry& rce) { return rce.title_id; });
add_on_content.erase(
std::remove_if(
add_on_content.begin(), add_on_content.end(),
@@ -50,6 +48,7 @@ static std::vector<u64> AccumulateAOCTitleIDs() {
}
AOC_U::AOC_U() : ServiceFramework("aoc:u"), add_on_content(AccumulateAOCTitleIDs()) {
+ // clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "CountAddOnContentByApplicationId"},
{1, nullptr, "ListAddOnContentByApplicationId"},
@@ -60,18 +59,29 @@ AOC_U::AOC_U() : ServiceFramework("aoc:u"), add_on_content(AccumulateAOCTitleIDs
{6, nullptr, "PrepareAddOnContentByApplicationId"},
{7, &AOC_U::PrepareAddOnContent, "PrepareAddOnContent"},
{8, &AOC_U::GetAddOnContentListChangedEvent, "GetAddOnContentListChangedEvent"},
+ {100, nullptr, "CreateEcPurchasedEventManager"},
};
+ // clang-format on
+
RegisterHandlers(functions);
auto& kernel = Core::System::GetInstance().Kernel();
- aoc_change_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Sticky,
+ aoc_change_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Manual,
"GetAddOnContentListChanged:Event");
}
AOC_U::~AOC_U() = default;
void AOC_U::CountAddOnContent(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_AOC, "called");
+ struct Parameters {
+ u64 process_id;
+ };
+ static_assert(sizeof(Parameters) == 8);
+
+ IPC::RequestParser rp{ctx};
+ const auto params = rp.PopRaw<Parameters>();
+
+ LOG_DEBUG(Service_AOC, "called. process_id={}", params.process_id);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
@@ -90,23 +100,32 @@ void AOC_U::CountAddOnContent(Kernel::HLERequestContext& ctx) {
}
void AOC_U::ListAddOnContent(Kernel::HLERequestContext& ctx) {
+ struct Parameters {
+ u32 offset;
+ u32 count;
+ u64 process_id;
+ };
+ static_assert(sizeof(Parameters) == 16);
+
IPC::RequestParser rp{ctx};
+ const auto [offset, count, process_id] = rp.PopRaw<Parameters>();
- const auto offset = rp.PopRaw<u32>();
- auto count = rp.PopRaw<u32>();
- LOG_DEBUG(Service_AOC, "called with offset={}, count={}", offset, count);
+ LOG_DEBUG(Service_AOC, "called with offset={}, count={}, process_id={}", offset, count,
+ process_id);
const auto current = Core::System::GetInstance().CurrentProcess()->GetTitleID();
std::vector<u32> out;
- for (size_t i = 0; i < add_on_content.size(); ++i) {
- if ((add_on_content[i] & DLC_BASE_TITLE_ID_MASK) == current)
- out.push_back(static_cast<u32>(add_on_content[i] & 0x7FF));
- }
-
const auto& disabled = Settings::values.disabled_addons[current];
- if (std::find(disabled.begin(), disabled.end(), "DLC") != disabled.end())
- out = {};
+ 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) {
+ continue;
+ }
+
+ out.push_back(static_cast<u32>(content_id & 0x7FF));
+ }
+ }
if (out.size() < offset) {
IPC::ResponseBuilder rb{ctx, 2};
@@ -115,22 +134,31 @@ void AOC_U::ListAddOnContent(Kernel::HLERequestContext& ctx) {
return;
}
- count = static_cast<u32>(std::min<size_t>(out.size() - offset, count));
+ const auto out_count = static_cast<u32>(std::min<size_t>(out.size() - offset, count));
std::rotate(out.begin(), out.begin() + offset, out.end());
- out.resize(count);
+ out.resize(out_count);
ctx.WriteBuffer(out);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- rb.Push(count);
+ rb.Push(out_count);
}
void AOC_U::GetAddOnContentBaseId(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_AOC, "called");
+ struct Parameters {
+ u64 process_id;
+ };
+ static_assert(sizeof(Parameters) == 8);
+
+ IPC::RequestParser rp{ctx};
+ const auto params = rp.PopRaw<Parameters>();
+
+ LOG_DEBUG(Service_AOC, "called. process_id={}", params.process_id);
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
+
const auto title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID();
FileSys::PatchManager pm{title_id};
@@ -144,10 +172,17 @@ void AOC_U::GetAddOnContentBaseId(Kernel::HLERequestContext& ctx) {
}
void AOC_U::PrepareAddOnContent(Kernel::HLERequestContext& ctx) {
+ struct Parameters {
+ s32 addon_index;
+ u64 process_id;
+ };
+ static_assert(sizeof(Parameters) == 16);
+
IPC::RequestParser rp{ctx};
+ const auto [addon_index, process_id] = rp.PopRaw<Parameters>();
- const auto aoc_id = rp.PopRaw<u32>();
- LOG_WARNING(Service_AOC, "(STUBBED) called with aoc_id={:08X}", aoc_id);
+ LOG_WARNING(Service_AOC, "(STUBBED) called with addon_index={}, process_id={}", addon_index,
+ process_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
diff --git a/src/core/hle/service/apm/interface.cpp b/src/core/hle/service/apm/interface.cpp
index fcacbab72..d058c0245 100644
--- a/src/core/hle/service/apm/interface.cpp
+++ b/src/core/hle/service/apm/interface.cpp
@@ -87,6 +87,8 @@ APM_Sys::APM_Sys() : ServiceFramework{"apm:sys"} {
{3, nullptr, "GetLastThrottlingState"},
{4, nullptr, "ClearLastThrottlingState"},
{5, nullptr, "LoadAndApplySettings"},
+ {6, nullptr, "SetCpuBoostMode"},
+ {7, nullptr, "GetCurrentPerformanceConfiguration"},
};
// clang-format on
diff --git a/src/core/hle/service/audio/audctl.cpp b/src/core/hle/service/audio/audctl.cpp
index b6b71f966..6a01d4d29 100644
--- a/src/core/hle/service/audio/audctl.cpp
+++ b/src/core/hle/service/audio/audctl.cpp
@@ -2,6 +2,8 @@
// 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/audio/audctl.h"
namespace Service::Audio {
@@ -11,8 +13,8 @@ AudCtl::AudCtl() : ServiceFramework{"audctl"} {
static const FunctionInfo functions[] = {
{0, nullptr, "GetTargetVolume"},
{1, nullptr, "SetTargetVolume"},
- {2, nullptr, "GetTargetVolumeMin"},
- {3, nullptr, "GetTargetVolumeMax"},
+ {2, &AudCtl::GetTargetVolumeMin, "GetTargetVolumeMin"},
+ {3, &AudCtl::GetTargetVolumeMax, "GetTargetVolumeMax"},
{4, nullptr, "IsTargetMute"},
{5, nullptr, "SetTargetMute"},
{6, nullptr, "IsTargetConnected"},
@@ -44,4 +46,28 @@ AudCtl::AudCtl() : ServiceFramework{"audctl"} {
AudCtl::~AudCtl() = default;
+void AudCtl::GetTargetVolumeMin(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Audio, "called.");
+
+ // This service function is currently hardcoded on the
+ // actual console to this value (as of 8.0.0).
+ constexpr s32 target_min_volume = 0;
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(target_min_volume);
+}
+
+void AudCtl::GetTargetVolumeMax(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Audio, "called.");
+
+ // This service function is currently hardcoded on the
+ // actual console to this value (as of 8.0.0).
+ constexpr s32 target_max_volume = 15;
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(target_max_volume);
+}
+
} // namespace Service::Audio
diff --git a/src/core/hle/service/audio/audctl.h b/src/core/hle/service/audio/audctl.h
index 9d2d9e83b..c7fafc02e 100644
--- a/src/core/hle/service/audio/audctl.h
+++ b/src/core/hle/service/audio/audctl.h
@@ -12,6 +12,10 @@ class AudCtl final : public ServiceFramework<AudCtl> {
public:
explicit AudCtl();
~AudCtl() override;
+
+private:
+ void GetTargetVolumeMin(Kernel::HLERequestContext& ctx);
+ void GetTargetVolumeMax(Kernel::HLERequestContext& ctx);
};
} // namespace Service::Audio
diff --git a/src/core/hle/service/audio/audin_u.cpp b/src/core/hle/service/audio/audin_u.cpp
index 088410564..d7f1d348d 100644
--- a/src/core/hle/service/audio/audin_u.cpp
+++ b/src/core/hle/service/audio/audin_u.cpp
@@ -2,9 +2,6 @@
// 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/audin_u.h"
namespace Service::Audio {
@@ -28,12 +25,12 @@ public:
{11, nullptr, "GetAudioInBufferCount"},
{12, nullptr, "SetAudioInDeviceGain"},
{13, nullptr, "GetAudioInDeviceGain"},
+ {14, nullptr, "FlushAudioInBuffers"},
};
// clang-format on
RegisterHandlers(functions);
}
- ~IAudioIn() = default;
};
AudInU::AudInU() : ServiceFramework("audin:u") {
diff --git a/src/core/hle/service/audio/audout_u.cpp b/src/core/hle/service/audio/audout_u.cpp
index bbe813490..7db6eb08d 100644
--- a/src/core/hle/service/audio/audout_u.cpp
+++ b/src/core/hle/service/audio/audout_u.cpp
@@ -44,7 +44,7 @@ public:
std::string&& unique_name)
: ServiceFramework("IAudioOut"), audio_core(audio_core),
device_name(std::move(device_name)), audio_params(audio_params) {
-
+ // clang-format off
static const FunctionInfo functions[] = {
{0, &IAudioOut::GetAudioOutState, "GetAudioOutState"},
{1, &IAudioOut::StartAudioOut, "StartAudioOut"},
@@ -58,13 +58,16 @@ public:
{9, &IAudioOut::GetAudioOutBufferCount, "GetAudioOutBufferCount"},
{10, nullptr, "GetAudioOutPlayedSampleCount"},
{11, nullptr, "FlushAudioOutBuffers"},
+ {12, &IAudioOut::SetAudioOutVolume, "SetAudioOutVolume"},
+ {13, &IAudioOut::GetAudioOutVolume, "GetAudioOutVolume"},
};
+ // clang-format on
RegisterHandlers(functions);
// This is the event handle used to check if the audio buffer was released
auto& system = Core::System::GetInstance();
buffer_event = Kernel::WritableEvent::CreateEventPair(
- system.Kernel(), Kernel::ResetType::Sticky, "IAudioOutBufferReleased");
+ system.Kernel(), Kernel::ResetType::Manual, "IAudioOutBufferReleased");
stream = audio_core.OpenStream(system.CoreTiming(), audio_params.sample_rate,
audio_params.channel_count, std::move(unique_name),
@@ -107,7 +110,9 @@ private:
void StopAudioOut(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Audio, "called");
- audio_core.StopStream(stream);
+ if (stream->IsPlaying()) {
+ audio_core.StopStream(stream);
+ }
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -148,7 +153,6 @@ private:
void GetReleasedAudioOutBufferImpl(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Audio, "called {}", ctx.Description());
- IPC::RequestParser rp{ctx};
const u64 max_count{ctx.GetWriteBufferSize() / sizeof(u64)};
const auto released_buffers{audio_core.GetTagsAndReleaseBuffers(stream, max_count)};
@@ -179,6 +183,25 @@ private:
rb.Push(static_cast<u32>(stream->GetQueueSize()));
}
+ void SetAudioOutVolume(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const float volume = rp.Pop<float>();
+ LOG_DEBUG(Service_Audio, "called, volume={}", volume);
+
+ stream->SetVolume(volume);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void GetAudioOutVolume(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Audio, "called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(stream->GetVolume());
+ }
+
AudioCore::AudioOut& audio_core;
AudioCore::StreamPtr stream;
std::string device_name;
@@ -192,12 +215,9 @@ private:
void AudOutU::ListAudioOutsImpl(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Audio, "called");
- IPC::RequestParser rp{ctx};
-
ctx.WriteBuffer(DefaultDevice);
IPC::ResponseBuilder rb{ctx, 3};
-
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(1); // Amount of audio devices
}
diff --git a/src/core/hle/service/audio/audrec_u.cpp b/src/core/hle/service/audio/audrec_u.cpp
index 6956a2e64..1a5aed9ed 100644
--- a/src/core/hle/service/audio/audrec_u.cpp
+++ b/src/core/hle/service/audio/audrec_u.cpp
@@ -2,9 +2,6 @@
// 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/audrec_u.h"
namespace Service::Audio {
@@ -30,7 +27,6 @@ public:
RegisterHandlers(functions);
}
- ~IFinalOutputRecorder() = default;
};
AudRecU::AudRecU() : ServiceFramework("audrec:u") {
diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp
index c9de10a24..75db0c2dc 100644
--- a/src/core/hle/service/audio/audren_u.cpp
+++ b/src/core/hle/service/audio/audren_u.cpp
@@ -8,8 +8,10 @@
#include "audio_core/audio_renderer.h"
#include "common/alignment.h"
+#include "common/bit_util.h"
#include "common/common_funcs.h"
#include "common/logging/log.h"
+#include "common/string_util.h"
#include "core/core.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/hle_ipc.h"
@@ -45,7 +47,7 @@ public:
auto& system = Core::System::GetInstance();
system_event = Kernel::WritableEvent::CreateEventPair(
- system.Kernel(), Kernel::ResetType::Sticky, "IAudioRenderer:SystemEvent");
+ system.Kernel(), Kernel::ResetType::Manual, "IAudioRenderer:SystemEvent");
renderer = std::make_unique<AudioCore::AudioRenderer>(system.CoreTiming(), audren_params,
system_event.writable);
}
@@ -177,14 +179,13 @@ public:
RegisterHandlers(functions);
auto& kernel = Core::System::GetInstance().Kernel();
- buffer_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::OneShot,
+ buffer_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Automatic,
"IAudioOutBufferReleasedEvent");
}
private:
void ListAudioDeviceName(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_Audio, "(STUBBED) called");
- IPC::RequestParser rp{ctx};
constexpr std::array<char, 15> audio_interface{{"AudioInterface"}};
ctx.WriteBuffer(audio_interface);
@@ -195,13 +196,13 @@ private:
}
void SetAudioDeviceOutputVolume(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_Audio, "(STUBBED) called");
-
IPC::RequestParser rp{ctx};
- f32 volume = static_cast<f32>(rp.Pop<u32>());
+ const f32 volume = rp.Pop<f32>();
- auto file_buffer = ctx.ReadBuffer();
- auto end = std::find(file_buffer.begin(), file_buffer.end(), '\0');
+ const auto device_name_buffer = ctx.ReadBuffer();
+ const std::string name = Common::StringFromBuffer(device_name_buffer);
+
+ LOG_WARNING(Service_Audio, "(STUBBED) called. name={}, volume={}", name, volume);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -209,7 +210,6 @@ private:
void GetActiveAudioDeviceName(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_Audio, "(STUBBED) called");
- IPC::RequestParser rp{ctx};
constexpr std::array<char, 12> audio_interface{{"AudioDevice"}};
ctx.WriteBuffer(audio_interface);
@@ -263,64 +263,304 @@ void AudRenU::OpenAudioRenderer(Kernel::HLERequestContext& ctx) {
OpenAudioRendererImpl(ctx);
}
+static u64 CalculateNumPerformanceEntries(const AudioCore::AudioRendererParameter& params) {
+ // +1 represents the final mix.
+ return u64{params.effect_count} + params.submix_count + params.sink_count + params.voice_count +
+ 1;
+}
+
void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- auto params = rp.PopRaw<AudioCore::AudioRendererParameter>();
LOG_DEBUG(Service_Audio, "called");
- u64 buffer_sz = Common::AlignUp(4 * params.mix_buffer_count, 0x40);
- buffer_sz += params.submix_count * 1024;
- buffer_sz += 0x940 * (params.submix_count + 1);
- buffer_sz += 0x3F0 * params.voice_count;
- buffer_sz += Common::AlignUp(8 * (params.submix_count + 1), 0x10);
- buffer_sz += Common::AlignUp(8 * params.voice_count, 0x10);
- buffer_sz += Common::AlignUp(
- (0x3C0 * (params.sink_count + params.submix_count) + 4 * params.sample_count) *
- (params.mix_buffer_count + 6),
- 0x40);
-
- if (IsFeatureSupported(AudioFeatures::Splitter, params.revision)) {
- const u32 count = params.submix_count + 1;
- u64 node_count = Common::AlignUp(count, 0x40);
- const u64 node_state_buffer_sz =
- 4 * (node_count * node_count) + 0xC * node_count + 2 * (node_count / 8);
- u64 edge_matrix_buffer_sz = 0;
- node_count = Common::AlignUp(count * count, 0x40);
- if (node_count >> 31 != 0) {
- edge_matrix_buffer_sz = (node_count | 7) / 8;
- } else {
- edge_matrix_buffer_sz = node_count / 8;
+ // Several calculations below align the sizes being calculated
+ // onto a 64 byte boundary.
+ static constexpr u64 buffer_alignment_size = 64;
+
+ // Some calculations that calculate portions of the buffer
+ // that will contain information, on the other hand, align
+ // the result of some of their calcularions on a 16 byte boundary.
+ static constexpr u64 info_field_alignment_size = 16;
+
+ // Maximum detail entries that may exist at one time for performance
+ // frame statistics.
+ static constexpr u64 max_perf_detail_entries = 100;
+
+ // Size of the data structure representing the bulk of the voice-related state.
+ static constexpr u64 voice_state_size = 0x100;
+
+ // Size of the upsampler manager data structure
+ 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) {
+ // As of 8.0.0 this is the maximum on voice channels.
+ constexpr u64 max_voice_channels = 6;
+
+ // The service expects the sample_count member of the parameters to either be
+ // a value of 160 or 240, so the maximum sample count is assumed in order
+ // to adequately handle all values at runtime.
+ constexpr u64 default_max_sample_count = 240;
+
+ const u64 total_mix_buffers = params.mix_buffer_count + max_voice_channels;
+
+ u64 size = 0;
+ size += total_mix_buffers * (sizeof(s32) * params.sample_count);
+ size += total_mix_buffers * (sizeof(s32) * default_max_sample_count);
+ size += u64{params.submix_count} + params.sink_count;
+ size = Common::AlignUp(size, buffer_alignment_size);
+ size += Common::AlignUp(params.unknown_30, buffer_alignment_size);
+ size += Common::AlignUp(sizeof(s32) * params.mix_buffer_count, buffer_alignment_size);
+ return size;
+ };
+
+ // Calculates the portion of the size related to the mix data (and the sorting thereof).
+ const auto calculate_mix_info_size = [this](const AudioCore::AudioRendererParameter& params) {
+ // The size of the mixing info data structure.
+ constexpr u64 mix_info_size = 0x940;
+
+ // Consists of total submixes with the final mix included.
+ const u64 total_mix_count = u64{params.submix_count} + 1;
+
+ // The total number of effects that may be available to the audio renderer at any time.
+ constexpr u64 max_effects = 256;
+
+ // Calculates the part of the size related to the audio node state.
+ // This will only be used if the audio revision supports the splitter.
+ const auto calculate_node_state_size = [](std::size_t num_nodes) {
+ // Internally within a nodestate, it appears to use a data structure
+ // similar to a std::bitset<64> twice.
+ constexpr u64 bit_size = Common::BitSize<u64>();
+ constexpr u64 num_bitsets = 2;
+
+ // Node state instances have three states internally for performing
+ // depth-first searches of nodes. Initialized, Found, and Done Sorting.
+ constexpr u64 num_states = 3;
+
+ u64 size = 0;
+ size += (num_nodes * num_nodes) * sizeof(s32);
+ size += num_states * (num_nodes * sizeof(s32));
+ size += num_bitsets * (Common::AlignUp(num_nodes, bit_size) / Common::BitSize<u8>());
+ return size;
+ };
+
+ // Calculates the part of the size related to the adjacency (aka edge) matrix.
+ const auto calculate_edge_matrix_size = [](std::size_t num_nodes) {
+ return (num_nodes * num_nodes) * sizeof(s32);
+ };
+
+ u64 size = 0;
+ size += Common::AlignUp(sizeof(void*) * total_mix_count, info_field_alignment_size);
+ size += Common::AlignUp(mix_info_size * total_mix_count, info_field_alignment_size);
+ size += Common::AlignUp(sizeof(s32) * max_effects * params.submix_count,
+ info_field_alignment_size);
+
+ if (IsFeatureSupported(AudioFeatures::Splitter, params.revision)) {
+ size += Common::AlignUp(calculate_node_state_size(total_mix_count) +
+ calculate_edge_matrix_size(total_mix_count),
+ info_field_alignment_size);
}
- buffer_sz += Common::AlignUp(node_state_buffer_sz + edge_matrix_buffer_sz, 0x10);
- }
- buffer_sz += 0x20 * (params.effect_count + 4 * params.voice_count) + 0x50;
- if (IsFeatureSupported(AudioFeatures::Splitter, params.revision)) {
- buffer_sz += 0xE0 * params.num_splitter_send_channels;
- buffer_sz += 0x20 * params.splitter_count;
- buffer_sz += Common::AlignUp(4 * params.num_splitter_send_channels, 0x10);
- }
- buffer_sz = Common::AlignUp(buffer_sz, 0x40) + 0x170 * params.sink_count;
- u64 output_sz = buffer_sz + 0x280 * params.sink_count + 0x4B0 * params.effect_count +
- ((params.voice_count * 256) | 0x40);
-
- if (params.performance_frame_count >= 1) {
- output_sz = Common::AlignUp(((16 * params.sink_count + 16 * params.effect_count +
- 16 * params.voice_count + 16) +
- 0x658) *
- (params.performance_frame_count + 1) +
- 0xc0,
- 0x40) +
- output_sz;
- }
- output_sz = Common::AlignUp(output_sz + 0x1807e, 0x1000);
+ return size;
+ };
- IPC::ResponseBuilder rb{ctx, 4};
+ // Calculates the part of the size related to voice channel info.
+ const auto calculate_voice_info_size = [](const AudioCore::AudioRendererParameter& params) {
+ constexpr u64 voice_info_size = 0x220;
+ constexpr u64 voice_resource_size = 0xD0;
+
+ u64 size = 0;
+ size += Common::AlignUp(sizeof(void*) * params.voice_count, info_field_alignment_size);
+ size += Common::AlignUp(voice_info_size * params.voice_count, info_field_alignment_size);
+ size +=
+ Common::AlignUp(voice_resource_size * params.voice_count, info_field_alignment_size);
+ size += Common::AlignUp(voice_state_size * params.voice_count, info_field_alignment_size);
+ return size;
+ };
+
+ // Calculates the part of the size related to memory pools.
+ const auto calculate_memory_pools_size = [](const AudioCore::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);
+ };
+
+ // Calculates the part of the size related to the splitter context.
+ const auto calculate_splitter_context_size =
+ [this](const AudioCore::AudioRendererParameter& params) -> u64 {
+ if (!IsFeatureSupported(AudioFeatures::Splitter, params.revision)) {
+ return 0;
+ }
+
+ constexpr u64 splitter_info_size = 0x20;
+ constexpr u64 splitter_destination_data_size = 0xE0;
+
+ u64 size = 0;
+ size += params.num_splitter_send_channels;
+ size +=
+ Common::AlignUp(splitter_info_size * params.splitter_count, info_field_alignment_size);
+ size += Common::AlignUp(splitter_destination_data_size * params.num_splitter_send_channels,
+ info_field_alignment_size);
+ return size;
+ };
+
+ // 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);
+ };
+
+ // Calculates the part of the size related to effect info.
+ const auto calculate_effect_info_size = [](const AudioCore::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 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 u64 voice_state_size = 0x100;
+ const u64 additional_size = buffer_alignment_size - 1;
+ return Common::AlignUp(voice_state_size * params.voice_count + additional_size,
+ info_field_alignment_size);
+ };
+
+ // Calculates the part of the size related to performance statistics.
+ const auto calculate_perf_size = [this](const AudioCore::AudioRendererParameter& params) {
+ // Extra size value appended to the end of the calculation.
+ constexpr u64 appended = 128;
+
+ // Whether or not we assume the newer version of performance metrics data structures.
+ const bool is_v2 =
+ IsFeatureSupported(AudioFeatures::PerformanceMetricsVersion2, params.revision);
+
+ // Data structure sizes
+ constexpr u64 perf_statistics_size = 0x0C;
+ const u64 header_size = is_v2 ? 0x30 : 0x18;
+ const u64 entry_size = is_v2 ? 0x18 : 0x10;
+ const u64 detail_size = is_v2 ? 0x18 : 0x10;
+
+ const u64 entry_count = CalculateNumPerformanceEntries(params);
+ const u64 size_per_frame =
+ header_size + (entry_size * entry_count) + (detail_size * max_perf_detail_entries);
+
+ u64 size = 0;
+ size += Common::AlignUp(size_per_frame * params.performance_frame_count + 1,
+ buffer_alignment_size);
+ size += Common::AlignUp(perf_statistics_size, buffer_alignment_size);
+ size += appended;
+ return size;
+ };
+
+ // Calculates the part of the size that relates to the audio command buffer.
+ const auto calculate_command_buffer_size =
+ [this](const AudioCore::AudioRendererParameter& params) {
+ constexpr u64 alignment = (buffer_alignment_size - 1) * 2;
+
+ if (!IsFeatureSupported(AudioFeatures::VariadicCommandBuffer, params.revision)) {
+ constexpr u64 command_buffer_size = 0x18000;
+
+ 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.
+
+ constexpr u64 max_biquad_filters = 2;
+ constexpr u64 max_mix_buffers = 24;
+
+ constexpr u64 biquad_filter_command_size = 0x2C;
+
+ constexpr u64 depop_mix_command_size = 0x24;
+ constexpr u64 depop_setup_command_size = 0x50;
+
+ 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 perf_command_size = 0x28;
+
+ 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 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);
+
+ // 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 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 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 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;
+ };
+
+ IPC::RequestParser rp{ctx};
+ const auto params = rp.PopRaw<AudioCore::AudioRendererParameter>();
+
+ u64 size = 0;
+ size += calculate_mix_buffer_sizes(params);
+ size += calculate_mix_info_size(params);
+ size += calculate_voice_info_size(params);
+ size += upsampler_manager_size;
+ size += calculate_memory_pools_size(params);
+ size += calculate_splitter_context_size(params);
+
+ size = Common::AlignUp(size, buffer_alignment_size);
+
+ size += calculate_upsampler_info_size(params);
+ size += calculate_effect_info_size(params);
+ size += calculate_sink_info_size(params);
+ size += calculate_voice_state_size(params);
+ size += calculate_perf_size(params);
+ size += calculate_command_buffer_size(params);
+
+ // finally, 4KB page align the size, and we're done.
+ size = Common::AlignUp(size, 4096);
+
+ IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
- rb.Push<u64>(output_sz);
+ rb.Push<u64>(size);
- LOG_DEBUG(Service_Audio, "buffer_size=0x{:X}", output_sz);
+ LOG_DEBUG(Service_Audio, "buffer_size=0x{:X}", size);
}
void AudRenU::GetAudioDeviceService(Kernel::HLERequestContext& ctx) {
@@ -358,10 +598,15 @@ void AudRenU::OpenAudioRendererImpl(Kernel::HLERequestContext& ctx) {
}
bool AudRenU::IsFeatureSupported(AudioFeatures feature, u32_le revision) const {
- u32_be version_num = (revision - Common::MakeMagic('R', 'E', 'V', '0')); // Byte swap
+ // Byte swap
+ const u32_be version_num = revision - Common::MakeMagic('R', 'E', 'V', '0');
+
switch (feature) {
case AudioFeatures::Splitter:
- return version_num >= 2u;
+ return version_num >= 2U;
+ case AudioFeatures::PerformanceMetricsVersion2:
+ case AudioFeatures::VariadicCommandBuffer:
+ return version_num >= 5U;
default:
return false;
}
diff --git a/src/core/hle/service/audio/audren_u.h b/src/core/hle/service/audio/audren_u.h
index e55d25973..1d3c8df61 100644
--- a/src/core/hle/service/audio/audren_u.h
+++ b/src/core/hle/service/audio/audren_u.h
@@ -28,6 +28,8 @@ private:
enum class AudioFeatures : u32 {
Splitter,
+ PerformanceMetricsVersion2,
+ VariadicCommandBuffer,
};
bool IsFeatureSupported(AudioFeatures feature, u32_le revision) const;
diff --git a/src/core/hle/service/audio/hwopus.cpp b/src/core/hle/service/audio/hwopus.cpp
index 11eba4a12..cb4a1160d 100644
--- a/src/core/hle/service/audio/hwopus.cpp
+++ b/src/core/hle/service/audio/hwopus.cpp
@@ -8,44 +8,34 @@
#include <vector>
#include <opus.h>
+#include <opus_multistream.h>
-#include "common/common_funcs.h"
+#include "common/assert.h"
#include "common/logging/log.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/service/audio/hwopus.h"
namespace Service::Audio {
-
+namespace {
struct OpusDeleter {
- void operator()(void* ptr) const {
- operator delete(ptr);
+ void operator()(OpusMSDecoder* ptr) const {
+ opus_multistream_decoder_destroy(ptr);
}
};
-class IHardwareOpusDecoderManager final : public ServiceFramework<IHardwareOpusDecoderManager> {
-public:
- IHardwareOpusDecoderManager(std::unique_ptr<OpusDecoder, OpusDeleter> decoder, u32 sample_rate,
- u32 channel_count)
- : ServiceFramework("IHardwareOpusDecoderManager"), decoder(std::move(decoder)),
- sample_rate(sample_rate), channel_count(channel_count) {
- // clang-format off
- static const FunctionInfo functions[] = {
- {0, &IHardwareOpusDecoderManager::DecodeInterleavedOld, "DecodeInterleavedOld"},
- {1, nullptr, "SetContext"},
- {2, nullptr, "DecodeInterleavedForMultiStreamOld"},
- {3, nullptr, "SetContextForMultiStream"},
- {4, &IHardwareOpusDecoderManager::DecodeInterleavedWithPerfOld, "DecodeInterleavedWithPerfOld"},
- {5, nullptr, "DecodeInterleavedForMultiStreamWithPerfOld"},
- {6, &IHardwareOpusDecoderManager::DecodeInterleaved, "DecodeInterleaved"},
- {7, nullptr, "DecodeInterleavedForMultiStream"},
- };
- // clang-format on
+using OpusDecoderPtr = std::unique_ptr<OpusMSDecoder, OpusDeleter>;
- RegisterHandlers(functions);
- }
+struct OpusPacketHeader {
+ // Packet size in bytes.
+ u32_be size;
+ // Indicates the final range of the codec's entropy coder.
+ u32_be final_range;
+};
+static_assert(sizeof(OpusPacketHeader) == 0x8, "OpusHeader is an invalid size");
-private:
+class OpusDecoderState {
+public:
/// Describes extra behavior that may be asked of the decoding context.
enum class ExtraBehavior {
/// No extra behavior.
@@ -55,30 +45,27 @@ private:
ResetContext,
};
- void DecodeInterleavedOld(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Audio, "called");
-
- DecodeInterleavedHelper(ctx, nullptr, ExtraBehavior::None);
- }
-
- void DecodeInterleavedWithPerfOld(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Audio, "called");
-
- u64 performance = 0;
- DecodeInterleavedHelper(ctx, &performance, ExtraBehavior::None);
- }
-
- void DecodeInterleaved(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Audio, "called");
-
- IPC::RequestParser rp{ctx};
- const auto extra_behavior =
- rp.Pop<bool>() ? ExtraBehavior::ResetContext : ExtraBehavior::None;
+ enum class PerfTime {
+ Disabled,
+ Enabled,
+ };
- u64 performance = 0;
- DecodeInterleavedHelper(ctx, &performance, extra_behavior);
+ explicit OpusDecoderState(OpusDecoderPtr decoder, u32 sample_rate, u32 channel_count)
+ : decoder{std::move(decoder)}, sample_rate{sample_rate}, channel_count{channel_count} {}
+
+ // Decodes interleaved Opus packets. Optionally allows reporting time taken to
+ // perform the decoding, as well as any relevant extra behavior.
+ void DecodeInterleaved(Kernel::HLERequestContext& ctx, PerfTime perf_time,
+ ExtraBehavior extra_behavior) {
+ if (perf_time == PerfTime::Disabled) {
+ DecodeInterleavedHelper(ctx, nullptr, extra_behavior);
+ } else {
+ u64 performance = 0;
+ DecodeInterleavedHelper(ctx, &performance, extra_behavior);
+ }
}
+private:
void DecodeInterleavedHelper(Kernel::HLERequestContext& ctx, u64* performance,
ExtraBehavior extra_behavior) {
u32 consumed = 0;
@@ -89,8 +76,7 @@ private:
ResetDecoderContext();
}
- if (!Decoder_DecodeInterleaved(consumed, sample_count, ctx.ReadBuffer(), samples,
- performance)) {
+ if (!DecodeOpusData(consumed, sample_count, ctx.ReadBuffer(), samples, performance)) {
LOG_ERROR(Audio, "Failed to decode opus data");
IPC::ResponseBuilder rb{ctx, 2};
// TODO(ogniK): Use correct error code
@@ -109,27 +95,27 @@ private:
ctx.WriteBuffer(samples.data(), samples.size() * sizeof(s16));
}
- bool Decoder_DecodeInterleaved(u32& consumed, u32& sample_count, const std::vector<u8>& input,
- std::vector<opus_int16>& output, u64* out_performance_time) {
+ bool DecodeOpusData(u32& consumed, u32& sample_count, const std::vector<u8>& input,
+ std::vector<opus_int16>& output, u64* out_performance_time) const {
const auto start_time = std::chrono::high_resolution_clock::now();
const std::size_t raw_output_sz = output.size() * sizeof(opus_int16);
- if (sizeof(OpusHeader) > input.size()) {
+ if (sizeof(OpusPacketHeader) > input.size()) {
LOG_ERROR(Audio, "Input is smaller than the header size, header_sz={}, input_sz={}",
- sizeof(OpusHeader), input.size());
+ sizeof(OpusPacketHeader), input.size());
return false;
}
- OpusHeader hdr{};
- std::memcpy(&hdr, input.data(), sizeof(OpusHeader));
- if (sizeof(OpusHeader) + static_cast<u32>(hdr.sz) > input.size()) {
+ OpusPacketHeader hdr{};
+ std::memcpy(&hdr, input.data(), sizeof(OpusPacketHeader));
+ if (sizeof(OpusPacketHeader) + static_cast<u32>(hdr.size) > input.size()) {
LOG_ERROR(Audio, "Input does not fit in the opus header size. data_sz={}, input_sz={}",
- sizeof(OpusHeader) + static_cast<u32>(hdr.sz), input.size());
+ sizeof(OpusPacketHeader) + static_cast<u32>(hdr.size), input.size());
return false;
}
- const auto frame = input.data() + sizeof(OpusHeader);
+ const auto frame = input.data() + sizeof(OpusPacketHeader);
const auto decoded_sample_count = opus_packet_get_nb_samples(
- frame, static_cast<opus_int32>(input.size() - sizeof(OpusHeader)),
+ frame, static_cast<opus_int32>(input.size() - sizeof(OpusPacketHeader)),
static_cast<opus_int32>(sample_rate));
if (decoded_sample_count * channel_count * sizeof(u16) > raw_output_sz) {
LOG_ERROR(
@@ -141,18 +127,18 @@ private:
const int frame_size = (static_cast<int>(raw_output_sz / sizeof(s16) / channel_count));
const auto out_sample_count =
- opus_decode(decoder.get(), frame, hdr.sz, output.data(), frame_size, 0);
+ opus_multistream_decode(decoder.get(), frame, hdr.size, output.data(), frame_size, 0);
if (out_sample_count < 0) {
LOG_ERROR(Audio,
"Incorrect sample count received from opus_decode, "
"output_sample_count={}, frame_size={}, data_sz_from_hdr={}",
- out_sample_count, frame_size, static_cast<u32>(hdr.sz));
+ out_sample_count, frame_size, static_cast<u32>(hdr.size));
return false;
}
const auto end_time = std::chrono::high_resolution_clock::now() - start_time;
sample_count = out_sample_count;
- consumed = static_cast<u32>(sizeof(OpusHeader) + hdr.sz);
+ consumed = static_cast<u32>(sizeof(OpusPacketHeader) + hdr.size);
if (out_performance_time != nullptr) {
*out_performance_time =
std::chrono::duration_cast<std::chrono::milliseconds>(end_time).count();
@@ -164,25 +150,86 @@ private:
void ResetDecoderContext() {
ASSERT(decoder != nullptr);
- opus_decoder_ctl(decoder.get(), OPUS_RESET_STATE);
+ opus_multistream_decoder_ctl(decoder.get(), OPUS_RESET_STATE);
}
- struct OpusHeader {
- u32_be sz; // Needs to be BE for some odd reason
- INSERT_PADDING_WORDS(1);
- };
- static_assert(sizeof(OpusHeader) == 0x8, "OpusHeader is an invalid size");
-
- std::unique_ptr<OpusDecoder, OpusDeleter> decoder;
+ OpusDecoderPtr decoder;
u32 sample_rate;
u32 channel_count;
};
-static std::size_t WorkerBufferSize(u32 channel_count) {
+class IHardwareOpusDecoderManager final : public ServiceFramework<IHardwareOpusDecoderManager> {
+public:
+ explicit IHardwareOpusDecoderManager(OpusDecoderState decoder_state)
+ : ServiceFramework("IHardwareOpusDecoderManager"), decoder_state{std::move(decoder_state)} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, &IHardwareOpusDecoderManager::DecodeInterleavedOld, "DecodeInterleavedOld"},
+ {1, nullptr, "SetContext"},
+ {2, nullptr, "DecodeInterleavedForMultiStreamOld"},
+ {3, nullptr, "SetContextForMultiStream"},
+ {4, &IHardwareOpusDecoderManager::DecodeInterleavedWithPerfOld, "DecodeInterleavedWithPerfOld"},
+ {5, nullptr, "DecodeInterleavedForMultiStreamWithPerfOld"},
+ {6, &IHardwareOpusDecoderManager::DecodeInterleaved, "DecodeInterleaved"},
+ {7, nullptr, "DecodeInterleavedForMultiStream"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+
+private:
+ void DecodeInterleavedOld(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Audio, "called");
+
+ decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Disabled,
+ OpusDecoderState::ExtraBehavior::None);
+ }
+
+ void DecodeInterleavedWithPerfOld(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Audio, "called");
+
+ decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Enabled,
+ OpusDecoderState::ExtraBehavior::None);
+ }
+
+ void DecodeInterleaved(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Audio, "called");
+
+ IPC::RequestParser rp{ctx};
+ const auto extra_behavior = rp.Pop<bool>() ? OpusDecoderState::ExtraBehavior::ResetContext
+ : OpusDecoderState::ExtraBehavior::None;
+
+ decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Enabled, extra_behavior);
+ }
+
+ OpusDecoderState decoder_state;
+};
+
+std::size_t WorkerBufferSize(u32 channel_count) {
ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count");
- return opus_decoder_get_size(static_cast<int>(channel_count));
+ constexpr int num_streams = 1;
+ const int num_stereo_streams = channel_count == 2 ? 1 : 0;
+ return opus_multistream_decoder_get_size(num_streams, num_stereo_streams);
}
+// Creates the mapping table that maps the input channels to the particular
+// output channels. In the stereo case, we map the left and right input channels
+// to the left and right output channels respectively.
+//
+// However, in the monophonic case, we only map the one available channel
+// to the sole output channel. We specify 255 for the would-be right channel
+// as this is a special value defined by Opus to indicate to the decoder to
+// ignore that channel.
+std::array<u8, 2> CreateMappingTable(u32 channel_count) {
+ if (channel_count == 2) {
+ return {{0, 1}};
+ }
+
+ return {{0, 255}};
+}
+} // Anonymous namespace
+
void HwOpus::GetWorkBufferSize(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto sample_rate = rp.Pop<u32>();
@@ -220,10 +267,15 @@ void HwOpus::OpenOpusDecoder(Kernel::HLERequestContext& ctx) {
const std::size_t worker_sz = WorkerBufferSize(channel_count);
ASSERT_MSG(buffer_sz >= worker_sz, "Worker buffer too large");
- std::unique_ptr<OpusDecoder, OpusDeleter> decoder{
- static_cast<OpusDecoder*>(operator new(worker_sz))};
- if (const int err = opus_decoder_init(decoder.get(), sample_rate, channel_count)) {
- LOG_ERROR(Audio, "Failed to init opus decoder with error={}", err);
+ const int num_stereo_streams = channel_count == 2 ? 1 : 0;
+ const auto mapping_table = CreateMappingTable(channel_count);
+
+ int error = 0;
+ OpusDecoderPtr decoder{
+ opus_multistream_decoder_create(sample_rate, static_cast<int>(channel_count), 1,
+ num_stereo_streams, mapping_table.data(), &error)};
+ if (error != OPUS_OK || decoder == nullptr) {
+ LOG_ERROR(Audio, "Failed to create Opus decoder (error={}).", error);
IPC::ResponseBuilder rb{ctx, 2};
// TODO(ogniK): Use correct error code
rb.Push(ResultCode(-1));
@@ -232,8 +284,8 @@ void HwOpus::OpenOpusDecoder(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IHardwareOpusDecoderManager>(std::move(decoder), sample_rate,
- channel_count);
+ rb.PushIpcInterface<IHardwareOpusDecoderManager>(
+ OpusDecoderState{std::move(decoder), sample_rate, channel_count});
}
HwOpus::HwOpus() : ServiceFramework("hwopus") {
diff --git a/src/core/hle/service/btdrv/btdrv.cpp b/src/core/hle/service/btdrv/btdrv.cpp
index 59ef603e1..3c7ca2c44 100644
--- a/src/core/hle/service/btdrv/btdrv.cpp
+++ b/src/core/hle/service/btdrv/btdrv.cpp
@@ -34,8 +34,8 @@ public:
RegisterHandlers(functions);
auto& kernel = Core::System::GetInstance().Kernel();
- register_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::OneShot,
- "BT:RegisterEvent");
+ register_event = Kernel::WritableEvent::CreateEventPair(
+ kernel, Kernel::ResetType::Automatic, "BT:RegisterEvent");
}
private:
@@ -154,7 +154,8 @@ public:
{96, nullptr, "GetLeHidEventInfo"},
{97, nullptr, "RegisterBleHidEvent"},
{98, nullptr, "SetLeScanParameter"},
- {256, nullptr, "GetIsManufacturingMode"}
+ {256, nullptr, "GetIsManufacturingMode"},
+ {257, nullptr, "EmulateBluetoothCrash"},
};
// clang-format on
diff --git a/src/core/hle/service/btm/btm.cpp b/src/core/hle/service/btm/btm.cpp
index 4f15c3f19..b439ee7ec 100644
--- a/src/core/hle/service/btm/btm.cpp
+++ b/src/core/hle/service/btm/btm.cpp
@@ -57,13 +57,13 @@ public:
RegisterHandlers(functions);
auto& kernel = Core::System::GetInstance().Kernel();
- scan_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::OneShot,
+ scan_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Automatic,
"IBtmUserCore:ScanEvent");
connection_event = Kernel::WritableEvent::CreateEventPair(
- kernel, Kernel::ResetType::OneShot, "IBtmUserCore:ConnectionEvent");
+ kernel, Kernel::ResetType::Automatic, "IBtmUserCore:ConnectionEvent");
service_discovery = Kernel::WritableEvent::CreateEventPair(
- kernel, Kernel::ResetType::OneShot, "IBtmUserCore:Discovery");
- config_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::OneShot,
+ kernel, Kernel::ResetType::Automatic, "IBtmUserCore:Discovery");
+ config_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Automatic,
"IBtmUserCore:ConfigEvent");
}
diff --git a/src/core/hle/service/caps/caps.cpp b/src/core/hle/service/caps/caps.cpp
index ae7b0720b..907f464ab 100644
--- a/src/core/hle/service/caps/caps.cpp
+++ b/src/core/hle/service/caps/caps.cpp
@@ -15,32 +15,41 @@ public:
explicit CAPS_A() : ServiceFramework{"caps:a"} {
// clang-format off
static const FunctionInfo functions[] = {
- {0, nullptr, "Unknown1"},
- {1, nullptr, "Unknown2"},
- {2, nullptr, "Unknown3"},
- {3, nullptr, "Unknown4"},
- {4, nullptr, "Unknown5"},
- {5, nullptr, "Unknown6"},
- {6, nullptr, "Unknown7"},
- {7, nullptr, "Unknown8"},
- {8, nullptr, "Unknown9"},
- {9, nullptr, "Unknown10"},
- {10, nullptr, "Unknown11"},
- {11, nullptr, "Unknown12"},
- {12, nullptr, "Unknown13"},
- {13, nullptr, "Unknown14"},
- {14, nullptr, "Unknown15"},
- {301, nullptr, "Unknown16"},
- {401, nullptr, "Unknown17"},
- {501, nullptr, "Unknown18"},
- {1001, nullptr, "Unknown19"},
- {1002, nullptr, "Unknown20"},
- {8001, nullptr, "Unknown21"},
- {8002, nullptr, "Unknown22"},
- {8011, nullptr, "Unknown23"},
- {8012, nullptr, "Unknown24"},
- {8021, nullptr, "Unknown25"},
- {10011, nullptr, "Unknown26"},
+ {0, nullptr, "GetAlbumFileCount"},
+ {1, nullptr, "GetAlbumFileList"},
+ {2, nullptr, "LoadAlbumFile"},
+ {3, nullptr, "DeleteAlbumFile"},
+ {4, nullptr, "StorageCopyAlbumFile"},
+ {5, nullptr, "IsAlbumMounted"},
+ {6, nullptr, "GetAlbumUsage"},
+ {7, nullptr, "GetAlbumFileSize"},
+ {8, nullptr, "LoadAlbumFileThumbnail"},
+ {9, nullptr, "LoadAlbumScreenShotImage"},
+ {10, nullptr, "LoadAlbumScreenShotThumbnailImage"},
+ {11, nullptr, "GetAlbumEntryFromApplicationAlbumEntry"},
+ {12, nullptr, "Unknown12"},
+ {13, nullptr, "Unknown13"},
+ {14, nullptr, "Unknown14"},
+ {15, nullptr, "Unknown15"},
+ {16, nullptr, "Unknown16"},
+ {17, nullptr, "Unknown17"},
+ {18, nullptr, "Unknown18"},
+ {202, nullptr, "SaveEditedScreenShot"},
+ {301, nullptr, "GetLastThumbnail"},
+ {401, nullptr, "GetAutoSavingStorage"},
+ {501, nullptr, "GetRequiredStorageSpaceSizeToCopyAll"},
+ {1001, nullptr, "Unknown1001"},
+ {1002, nullptr, "Unknown1002"},
+ {1003, nullptr, "Unknown1003"},
+ {8001, nullptr, "ForceAlbumUnmounted"},
+ {8002, nullptr, "ResetAlbumMountStatus"},
+ {8011, nullptr, "RefreshAlbumCache"},
+ {8012, nullptr, "GetAlbumCache"},
+ {8013, nullptr, "Unknown8013"},
+ {8021, nullptr, "GetAlbumEntryFromApplicationAlbumEntryAruid"},
+ {10011, nullptr, "SetInternalErrorConversionEnabled"},
+ {50000, nullptr, "Unknown50000"},
+ {60002, nullptr, "Unknown60002"},
};
// clang-format on
@@ -53,16 +62,17 @@ public:
explicit CAPS_C() : ServiceFramework{"caps:c"} {
// clang-format off
static const FunctionInfo functions[] = {
- {2001, nullptr, "Unknown1"},
- {2002, nullptr, "Unknown2"},
- {2011, nullptr, "Unknown3"},
- {2012, nullptr, "Unknown4"},
- {2013, nullptr, "Unknown5"},
- {2014, nullptr, "Unknown6"},
- {2101, nullptr, "Unknown7"},
- {2102, nullptr, "Unknown8"},
- {2201, nullptr, "Unknown9"},
- {2301, nullptr, "Unknown10"},
+ {33, nullptr, "Unknown33"},
+ {2001, nullptr, "Unknown2001"},
+ {2002, nullptr, "Unknown2002"},
+ {2011, nullptr, "Unknown2011"},
+ {2012, nullptr, "Unknown2012"},
+ {2013, nullptr, "Unknown2013"},
+ {2014, nullptr, "Unknown2014"},
+ {2101, nullptr, "Unknown2101"},
+ {2102, nullptr, "Unknown2102"},
+ {2201, nullptr, "Unknown2201"},
+ {2301, nullptr, "Unknown2301"},
};
// clang-format on
@@ -127,11 +137,18 @@ public:
explicit CAPS_U() : ServiceFramework{"caps:u"} {
// clang-format off
static const FunctionInfo functions[] = {
+ {32, nullptr, "SetShimLibraryVersion"},
{102, nullptr, "GetAlbumFileListByAruid"},
{103, nullptr, "DeleteAlbumFileByAruid"},
{104, nullptr, "GetAlbumFileSizeByAruid"},
+ {105, nullptr, "DeleteAlbumFileByAruidForDebug"},
{110, nullptr, "LoadAlbumScreenShotImageByAruid"},
{120, nullptr, "LoadAlbumScreenShotThumbnailImageByAruid"},
+ {130, nullptr, "PrecheckToCreateContentsByAruid"},
+ {140, nullptr, "GetAlbumFileList1AafeAruidDeprecated"},
+ {141, nullptr, "GetAlbumFileList2AafeUidAruidDeprecated"},
+ {142, nullptr, "GetAlbumFileList3AaeAruid"},
+ {143, nullptr, "GetAlbumFileList4AaeUidAruid"},
{60002, nullptr, "OpenAccessorSessionForApplication"},
};
// clang-format on
diff --git a/src/core/hle/service/fatal/fatal.cpp b/src/core/hle/service/fatal/fatal.cpp
index 770590d0b..2c229bcad 100644
--- a/src/core/hle/service/fatal/fatal.cpp
+++ b/src/core/hle/service/fatal/fatal.cpp
@@ -25,21 +25,34 @@ Module::Interface::Interface(std::shared_ptr<Module> module, const char* name)
Module::Interface::~Interface() = default;
struct FatalInfo {
- std::array<u64_le, 31> registers{}; // TODO(ogniK): See if this actually is registers or
- // not(find a game which has non zero valeus)
- u64_le unk0{};
- u64_le unk1{};
- u64_le unk2{};
- u64_le unk3{};
- u64_le unk4{};
- u64_le unk5{};
- u64_le unk6{};
+ enum class Architecture : s32 {
+ AArch64,
+ AArch32,
+ };
+
+ const char* ArchAsString() const {
+ return arch == Architecture::AArch64 ? "AArch64" : "AArch32";
+ }
+
+ std::array<u64_le, 31> registers{};
+ u64_le sp{};
+ u64_le pc{};
+ u64_le pstate{};
+ u64_le afsr0{};
+ u64_le afsr1{};
+ u64_le esr{};
+ u64_le far{};
std::array<u64_le, 32> backtrace{};
- u64_le unk7{};
- u64_le unk8{};
+ u64_le program_entry_point{};
+
+ // Bit flags that indicate which registers have been set with values
+ // for this context. The service itself uses these to determine which
+ // registers to specifically print out.
+ u64_le set_flags{};
+
u32_le backtrace_size{};
- u32_le unk9{};
+ Architecture arch{};
u32_le unk10{}; // TODO(ogniK): Is this even used or is it just padding?
};
static_assert(sizeof(FatalInfo) == 0x250, "FatalInfo is an invalid size");
@@ -52,36 +65,36 @@ enum class FatalType : u32 {
static void GenerateErrorReport(ResultCode error_code, const FatalInfo& info) {
const auto title_id = Core::CurrentProcess()->GetTitleID();
- std::string crash_report =
- fmt::format("Yuzu {}-{} crash report\n"
- "Title ID: {:016x}\n"
- "Result: 0x{:X} ({:04}-{:04d})\n"
- "\n",
- Common::g_scm_branch, Common::g_scm_desc, title_id, error_code.raw,
- 2000 + static_cast<u32>(error_code.module.Value()),
- static_cast<u32>(error_code.description.Value()), info.unk8, info.unk7);
+ std::string crash_report = fmt::format(
+ "Yuzu {}-{} crash report\n"
+ "Title ID: {:016x}\n"
+ "Result: 0x{:X} ({:04}-{:04d})\n"
+ "Set flags: 0x{:16X}\n"
+ "Program entry point: 0x{:16X}\n"
+ "\n",
+ Common::g_scm_branch, Common::g_scm_desc, title_id, error_code.raw,
+ 2000 + static_cast<u32>(error_code.module.Value()),
+ static_cast<u32>(error_code.description.Value()), info.set_flags, info.program_entry_point);
if (info.backtrace_size != 0x0) {
crash_report += "Registers:\n";
- // TODO(ogniK): This is just a guess, find a game which actually has non zero values
for (size_t i = 0; i < info.registers.size(); i++) {
crash_report +=
fmt::format(" X[{:02d}]: {:016x}\n", i, info.registers[i]);
}
- crash_report += fmt::format(" Unknown 0: {:016x}\n", info.unk0);
- crash_report += fmt::format(" Unknown 1: {:016x}\n", info.unk1);
- crash_report += fmt::format(" Unknown 2: {:016x}\n", info.unk2);
- crash_report += fmt::format(" Unknown 3: {:016x}\n", info.unk3);
- crash_report += fmt::format(" Unknown 4: {:016x}\n", info.unk4);
- crash_report += fmt::format(" Unknown 5: {:016x}\n", info.unk5);
- crash_report += fmt::format(" Unknown 6: {:016x}\n", info.unk6);
+ crash_report += fmt::format(" SP: {:016x}\n", info.sp);
+ crash_report += fmt::format(" PC: {:016x}\n", info.pc);
+ crash_report += fmt::format(" PSTATE: {:016x}\n", info.pstate);
+ crash_report += fmt::format(" AFSR0: {:016x}\n", info.afsr0);
+ crash_report += fmt::format(" AFSR1: {:016x}\n", info.afsr1);
+ crash_report += fmt::format(" ESR: {:016x}\n", info.esr);
+ crash_report += fmt::format(" FAR: {:016x}\n", info.far);
crash_report += "\nBacktrace:\n";
for (size_t i = 0; i < info.backtrace_size; i++) {
crash_report +=
fmt::format(" Backtrace[{:02d}]: {:016x}\n", i, info.backtrace[i]);
}
- crash_report += fmt::format("\nUnknown 7: 0x{:016x}\n", info.unk7);
- crash_report += fmt::format("Unknown 8: 0x{:016x}\n", info.unk8);
- crash_report += fmt::format("Unknown 9: 0x{:016x}\n", info.unk9);
+
+ crash_report += fmt::format("Architecture: {}\n", info.ArchAsString());
crash_report += fmt::format("Unknown 10: 0x{:016x}\n", info.unk10);
}
@@ -125,13 +138,13 @@ static void ThrowFatalError(ResultCode error_code, FatalType fatal_type, const F
case FatalType::ErrorReport:
GenerateErrorReport(error_code, info);
break;
- };
+ }
}
void Module::Interface::ThrowFatal(Kernel::HLERequestContext& ctx) {
LOG_ERROR(Service_Fatal, "called");
IPC::RequestParser rp{ctx};
- auto error_code = rp.Pop<ResultCode>();
+ const auto error_code = rp.Pop<ResultCode>();
ThrowFatalError(error_code, FatalType::ErrorScreen, {});
IPC::ResponseBuilder rb{ctx, 2};
@@ -141,8 +154,8 @@ void Module::Interface::ThrowFatal(Kernel::HLERequestContext& ctx) {
void Module::Interface::ThrowFatalWithPolicy(Kernel::HLERequestContext& ctx) {
LOG_ERROR(Service_Fatal, "called");
IPC::RequestParser rp(ctx);
- auto error_code = rp.Pop<ResultCode>();
- auto fatal_type = rp.PopEnum<FatalType>();
+ const auto error_code = rp.Pop<ResultCode>();
+ const auto fatal_type = rp.PopEnum<FatalType>();
ThrowFatalError(error_code, fatal_type, {}); // No info is passed with ThrowFatalWithPolicy
IPC::ResponseBuilder rb{ctx, 2};
@@ -152,9 +165,9 @@ void Module::Interface::ThrowFatalWithPolicy(Kernel::HLERequestContext& ctx) {
void Module::Interface::ThrowFatalWithCpuContext(Kernel::HLERequestContext& ctx) {
LOG_ERROR(Service_Fatal, "called");
IPC::RequestParser rp(ctx);
- auto error_code = rp.Pop<ResultCode>();
- auto fatal_type = rp.PopEnum<FatalType>();
- auto fatal_info = ctx.ReadBuffer();
+ const auto error_code = rp.Pop<ResultCode>();
+ const auto fatal_type = rp.PopEnum<FatalType>();
+ const auto fatal_info = ctx.ReadBuffer();
FatalInfo info{};
ASSERT_MSG(fatal_info.size() == sizeof(FatalInfo), "Invalid fatal info buffer size!");
diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp
index c6da2df43..1ebfeb4bf 100644
--- a/src/core/hle/service/filesystem/filesystem.cpp
+++ b/src/core/hle/service/filesystem/filesystem.cpp
@@ -197,13 +197,16 @@ ResultCode VfsDirectoryServiceWrapper::RenameDirectory(const std::string& src_pa
ResultVal<FileSys::VirtualFile> VfsDirectoryServiceWrapper::OpenFile(const std::string& path_,
FileSys::Mode mode) const {
- std::string path(FileUtil::SanitizePath(path_));
- auto npath = path;
- while (npath.size() > 0 && (npath[0] == '/' || npath[0] == '\\'))
- npath = npath.substr(1);
+ const std::string path(FileUtil::SanitizePath(path_));
+ std::string_view npath = path;
+ while (!npath.empty() && (npath[0] == '/' || npath[0] == '\\')) {
+ npath.remove_prefix(1);
+ }
+
auto file = backing->GetFileRelative(npath);
- if (file == nullptr)
+ if (file == nullptr) {
return FileSys::ERROR_PATH_NOT_FOUND;
+ }
if (mode == FileSys::Mode::Append) {
return MakeResult<FileSys::VirtualFile>(
@@ -319,15 +322,15 @@ ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id, FileSys::StorageId stora
}
ResultVal<FileSys::VirtualDir> OpenSaveData(FileSys::SaveDataSpaceId space,
- FileSys::SaveDataDescriptor save_struct) {
+ const FileSys::SaveDataDescriptor& descriptor) {
LOG_TRACE(Service_FS, "Opening Save Data for space_id={:01X}, save_struct={}",
- static_cast<u8>(space), save_struct.DebugInfo());
+ static_cast<u8>(space), descriptor.DebugInfo());
if (save_data_factory == nullptr) {
return FileSys::ERROR_ENTITY_NOT_FOUND;
}
- return save_data_factory->Open(space, save_struct);
+ return save_data_factory->Open(space, descriptor);
}
ResultVal<FileSys::VirtualDir> OpenSaveDataSpace(FileSys::SaveDataSpaceId space) {
@@ -388,11 +391,6 @@ void WriteSaveDataSize(FileSys::SaveDataType type, u64 title_id, u128 user_id,
save_data_factory->WriteSaveDataSize(type, title_id, user_id, new_value);
}
-FileSys::RegisteredCacheUnion GetUnionContents() {
- return FileSys::RegisteredCacheUnion{
- {GetSystemNANDContents(), GetUserNANDContents(), GetSDMCContents()}};
-}
-
FileSys::RegisteredCache* GetSystemNANDContents() {
LOG_TRACE(Service_FS, "Opening System NAND Contents");
@@ -457,6 +455,10 @@ void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite) {
if (bis_factory == nullptr) {
bis_factory =
std::make_unique<FileSys::BISFactory>(nand_directory, load_directory, dump_directory);
+ Core::System::GetInstance().RegisterContentProvider(
+ FileSys::ContentProviderUnionSlot::SysNAND, bis_factory->GetSystemNANDContents());
+ Core::System::GetInstance().RegisterContentProvider(
+ FileSys::ContentProviderUnionSlot::UserNAND, bis_factory->GetUserNANDContents());
}
if (save_data_factory == nullptr) {
@@ -465,6 +467,8 @@ void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite) {
if (sdmc_factory == nullptr) {
sdmc_factory = std::make_unique<FileSys::SDMCFactory>(std::move(sd_directory));
+ Core::System::GetInstance().RegisterContentProvider(FileSys::ContentProviderUnionSlot::SDMC,
+ sdmc_factory->GetSDMCContents());
}
}
diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h
index 6fd5e7b23..6481f237c 100644
--- a/src/core/hle/service/filesystem/filesystem.h
+++ b/src/core/hle/service/filesystem/filesystem.h
@@ -46,7 +46,7 @@ ResultVal<FileSys::VirtualFile> OpenRomFSCurrentProcess();
ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id, FileSys::StorageId storage_id,
FileSys::ContentRecordType type);
ResultVal<FileSys::VirtualDir> OpenSaveData(FileSys::SaveDataSpaceId space,
- FileSys::SaveDataDescriptor save_struct);
+ const FileSys::SaveDataDescriptor& descriptor);
ResultVal<FileSys::VirtualDir> OpenSaveDataSpace(FileSys::SaveDataSpaceId space);
ResultVal<FileSys::VirtualDir> OpenSDMC();
@@ -54,8 +54,6 @@ FileSys::SaveDataSize ReadSaveDataSize(FileSys::SaveDataType type, u64 title_id,
void WriteSaveDataSize(FileSys::SaveDataType type, u64 title_id, u128 user_id,
FileSys::SaveDataSize new_value);
-FileSys::RegisteredCacheUnion GetUnionContents();
-
FileSys::RegisteredCache* GetSystemNANDContents();
FileSys::RegisteredCache* GetUserNANDContents();
FileSys::RegisteredCache* GetSDMCContents();
diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp
index 54959edd8..e7df8fd98 100644
--- a/src/core/hle/service/filesystem/fsp_srv.cpp
+++ b/src/core/hle/service/filesystem/fsp_srv.cpp
@@ -115,11 +115,12 @@ private:
void Read(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const u64 unk = rp.Pop<u64>();
+ const u64 option = rp.Pop<u64>();
const s64 offset = rp.Pop<s64>();
const s64 length = rp.Pop<s64>();
- LOG_DEBUG(Service_FS, "called, offset=0x{:X}, length={}", offset, length);
+ LOG_DEBUG(Service_FS, "called, option={}, offset=0x{:X}, length={}", option, offset,
+ length);
// Error checking
if (length < 0) {
@@ -148,11 +149,12 @@ private:
void Write(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const u64 unk = rp.Pop<u64>();
+ const u64 option = rp.Pop<u64>();
const s64 offset = rp.Pop<s64>();
const s64 length = rp.Pop<s64>();
- LOG_DEBUG(Service_FS, "called, offset=0x{:X}, length={}", offset, length);
+ LOG_DEBUG(Service_FS, "called, option={}, offset=0x{:X}, length={}", option, offset,
+ length);
// Error checking
if (length < 0) {
@@ -250,10 +252,7 @@ private:
u64 next_entry_index = 0;
void Read(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const u64 unk = rp.Pop<u64>();
-
- LOG_DEBUG(Service_FS, "called, unk=0x{:X}", unk);
+ LOG_DEBUG(Service_FS, "called.");
// Calculate how many entries we can fit in the output buffer
const u64 count_entries = ctx.GetWriteBufferSize() / sizeof(FileSys::Entry);
@@ -315,61 +314,53 @@ public:
void CreateFile(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- auto file_buffer = ctx.ReadBuffer();
- std::string name = Common::StringFromBuffer(file_buffer);
+ const auto file_buffer = ctx.ReadBuffer();
+ const std::string name = Common::StringFromBuffer(file_buffer);
- u64 mode = rp.Pop<u64>();
- u32 size = rp.Pop<u32>();
+ const u64 mode = rp.Pop<u64>();
+ const u32 size = rp.Pop<u32>();
- LOG_DEBUG(Service_FS, "called file {} mode 0x{:X} size 0x{:08X}", name, mode, size);
+ LOG_DEBUG(Service_FS, "called. file={}, mode=0x{:X}, size=0x{:08X}", name, mode, size);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(backend.CreateFile(name, size));
}
void DeleteFile(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
-
- auto file_buffer = ctx.ReadBuffer();
- std::string name = Common::StringFromBuffer(file_buffer);
+ const auto file_buffer = ctx.ReadBuffer();
+ const std::string name = Common::StringFromBuffer(file_buffer);
- LOG_DEBUG(Service_FS, "called file {}", name);
+ LOG_DEBUG(Service_FS, "called. file={}", name);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(backend.DeleteFile(name));
}
void CreateDirectory(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
-
- auto file_buffer = ctx.ReadBuffer();
- std::string name = Common::StringFromBuffer(file_buffer);
+ const auto file_buffer = ctx.ReadBuffer();
+ const std::string name = Common::StringFromBuffer(file_buffer);
- LOG_DEBUG(Service_FS, "called directory {}", name);
+ LOG_DEBUG(Service_FS, "called. directory={}", name);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(backend.CreateDirectory(name));
}
void DeleteDirectory(Kernel::HLERequestContext& ctx) {
- const IPC::RequestParser rp{ctx};
-
const auto file_buffer = ctx.ReadBuffer();
- std::string name = Common::StringFromBuffer(file_buffer);
+ const std::string name = Common::StringFromBuffer(file_buffer);
- LOG_DEBUG(Service_FS, "called directory {}", name);
+ LOG_DEBUG(Service_FS, "called. directory={}", name);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(backend.DeleteDirectory(name));
}
void DeleteDirectoryRecursively(Kernel::HLERequestContext& ctx) {
- const IPC::RequestParser rp{ctx};
-
const auto file_buffer = ctx.ReadBuffer();
- std::string name = Common::StringFromBuffer(file_buffer);
+ const std::string name = Common::StringFromBuffer(file_buffer);
- LOG_DEBUG(Service_FS, "called directory {}", name);
+ LOG_DEBUG(Service_FS, "called. directory={}", name);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(backend.DeleteDirectoryRecursively(name));
@@ -386,18 +377,16 @@ public:
}
void RenameFile(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
-
std::vector<u8> buffer;
buffer.resize(ctx.BufferDescriptorX()[0].Size());
Memory::ReadBlock(ctx.BufferDescriptorX()[0].Address(), buffer.data(), buffer.size());
- std::string src_name = Common::StringFromBuffer(buffer);
+ const std::string src_name = Common::StringFromBuffer(buffer);
buffer.resize(ctx.BufferDescriptorX()[1].Size());
Memory::ReadBlock(ctx.BufferDescriptorX()[1].Address(), buffer.data(), buffer.size());
- std::string dst_name = Common::StringFromBuffer(buffer);
+ const std::string dst_name = Common::StringFromBuffer(buffer);
- LOG_DEBUG(Service_FS, "called file '{}' to file '{}'", src_name, dst_name);
+ LOG_DEBUG(Service_FS, "called. file '{}' to file '{}'", src_name, dst_name);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(backend.RenameFile(src_name, dst_name));
@@ -406,12 +395,12 @@ public:
void OpenFile(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- auto file_buffer = ctx.ReadBuffer();
- std::string name = Common::StringFromBuffer(file_buffer);
+ const auto file_buffer = ctx.ReadBuffer();
+ const std::string name = Common::StringFromBuffer(file_buffer);
- auto mode = static_cast<FileSys::Mode>(rp.Pop<u32>());
+ 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, static_cast<u32>(mode));
auto result = backend.OpenFile(name, mode);
if (result.Failed()) {
@@ -430,13 +419,13 @@ public:
void OpenDirectory(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- auto file_buffer = ctx.ReadBuffer();
- std::string name = Common::StringFromBuffer(file_buffer);
+ const auto file_buffer = ctx.ReadBuffer();
+ const std::string name = Common::StringFromBuffer(file_buffer);
// TODO(Subv): Implement this filter.
- u32 filter_flags = rp.Pop<u32>();
+ const u32 filter_flags = rp.Pop<u32>();
- LOG_DEBUG(Service_FS, "called directory {} filter {}", name, filter_flags);
+ LOG_DEBUG(Service_FS, "called. directory={}, filter={}", name, filter_flags);
auto result = backend.OpenDirectory(name);
if (result.Failed()) {
@@ -453,12 +442,10 @@ public:
}
void GetEntryType(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
-
- auto file_buffer = ctx.ReadBuffer();
- std::string name = Common::StringFromBuffer(file_buffer);
+ const auto file_buffer = ctx.ReadBuffer();
+ const std::string name = Common::StringFromBuffer(file_buffer);
- LOG_DEBUG(Service_FS, "called file {}", name);
+ LOG_DEBUG(Service_FS, "called. file={}", name);
auto result = backend.GetEntryType(name);
if (result.Failed()) {
@@ -616,7 +603,9 @@ private:
u64_le save_id;
u64_le title_id;
u64_le save_image_size;
- INSERT_PADDING_BYTES(0x28);
+ u16_le index;
+ FileSys::SaveDataRank rank;
+ INSERT_PADDING_BYTES(0x25);
};
static_assert(sizeof(SaveDataInfo) == 0x60, "SaveDataInfo has incorrect size.");
@@ -675,10 +664,13 @@ FSP_SRV::FSP_SRV() : ServiceFramework("fsp-srv") {
{100, nullptr, "OpenImageDirectoryFileSystem"},
{110, nullptr, "OpenContentStorageFileSystem"},
{120, nullptr, "OpenCloudBackupWorkStorageFileSystem"},
+ {130, nullptr, "OpenCustomStorageFileSystem"},
{200, &FSP_SRV::OpenDataStorageByCurrentProcess, "OpenDataStorageByCurrentProcess"},
{201, nullptr, "OpenDataStorageByProgramId"},
{202, &FSP_SRV::OpenDataStorageByDataId, "OpenDataStorageByDataId"},
{203, &FSP_SRV::OpenPatchDataStorageByCurrentProcess, "OpenPatchDataStorageByCurrentProcess"},
+ {204, nullptr, "OpenDataFileSystemByProgramIndex"},
+ {205, nullptr, "OpenDataStorageByProgramIndex"},
{400, nullptr, "OpenDeviceOperator"},
{500, nullptr, "OpenSdCardDetectionEventNotifier"},
{501, nullptr, "OpenGameCardDetectionEventNotifier"},
@@ -702,6 +694,7 @@ FSP_SRV::FSP_SRV() : ServiceFramework("fsp-srv") {
{614, nullptr, "CorruptSaveDataFileSystemBySaveDataSpaceId"},
{615, nullptr, "QuerySaveDataInternalStorageTotalSize"},
{616, nullptr, "GetSaveDataCommitId"},
+ {617, nullptr, "UnregisterExternalKey"},
{620, nullptr, "SetSdCardEncryptionSeed"},
{630, nullptr, "SetSdCardAccessibility"},
{631, nullptr, "IsSdCardAccessible"},
@@ -712,6 +705,7 @@ FSP_SRV::FSP_SRV() : ServiceFramework("fsp-srv") {
{710, nullptr, "ResolveAccessFailure"},
{720, nullptr, "AbandonAccessFailure"},
{800, nullptr, "GetAndClearFileSystemProxyErrorInfo"},
+ {810, nullptr, "RegisterProgramIndexMapInfo"},
{1000, nullptr, "SetBisRootForHost"},
{1001, nullptr, "SetSaveDataSize"},
{1002, nullptr, "SetSaveDataRootPath"},
@@ -722,6 +716,8 @@ FSP_SRV::FSP_SRV() : ServiceFramework("fsp-srv") {
{1007, nullptr, "RegisterUpdatePartition"},
{1008, nullptr, "OpenRegisteredUpdatePartition"},
{1009, nullptr, "GetAndClearMemoryReportInfo"},
+ {1010, nullptr, "SetDataStorageRedirectTarget"},
+ {1011, nullptr, "OutputAccessLogToSdCard2"},
{1100, nullptr, "OverrideSaveDataTransferTokenSignVerificationKey"},
{1110, nullptr, "CorruptSaveDataFileSystemBySaveDataSpaceId2"},
{1200, nullptr, "OpenMultiCommitManager"},
@@ -733,7 +729,10 @@ FSP_SRV::FSP_SRV() : ServiceFramework("fsp-srv") {
FSP_SRV::~FSP_SRV() = default;
void FSP_SRV::SetCurrentProcess(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_FS, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ current_process_id = rp.Pop<u64>();
+
+ LOG_DEBUG(Service_FS, "called. current_process_id=0x{:016X}", current_process_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -776,16 +775,17 @@ void FSP_SRV::CreateSaveDataFileSystem(Kernel::HLERequestContext& ctx) {
}
void FSP_SRV::OpenSaveDataFileSystem(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
+ LOG_INFO(Service_FS, "called.");
- auto space_id = rp.PopRaw<FileSys::SaveDataSpaceId>();
- auto unk = rp.Pop<u32>();
- LOG_INFO(Service_FS, "called with unknown={:08X}", unk);
-
- auto save_struct = rp.PopRaw<FileSys::SaveDataDescriptor>();
+ struct Parameters {
+ FileSys::SaveDataSpaceId save_data_space_id;
+ FileSys::SaveDataDescriptor descriptor;
+ };
- auto dir = OpenSaveData(space_id, save_struct);
+ IPC::RequestParser rp{ctx};
+ const auto parameters = rp.PopRaw<Parameters>();
+ auto dir = OpenSaveData(parameters.save_data_space_id, parameters.descriptor);
if (dir.Failed()) {
IPC::ResponseBuilder rb{ctx, 2, 0, 0};
rb.Push(FileSys::ERROR_ENTITY_NOT_FOUND);
diff --git a/src/core/hle/service/filesystem/fsp_srv.h b/src/core/hle/service/filesystem/fsp_srv.h
index 3a5f4e200..d7572ba7a 100644
--- a/src/core/hle/service/filesystem/fsp_srv.h
+++ b/src/core/hle/service/filesystem/fsp_srv.h
@@ -32,6 +32,7 @@ private:
void OpenPatchDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx);
FileSys::VirtualFile romfs;
+ u64 current_process_id = 0;
};
} // namespace Service::FileSystem
diff --git a/src/core/hle/service/friend/friend.cpp b/src/core/hle/service/friend/friend.cpp
index d9225d624..5100e376c 100644
--- a/src/core/hle/service/friend/friend.cpp
+++ b/src/core/hle/service/friend/friend.cpp
@@ -12,6 +12,7 @@ namespace Service::Friend {
class IFriendService final : public ServiceFramework<IFriendService> {
public:
IFriendService() : ServiceFramework("IFriendService") {
+ // clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetCompletionEvent"},
{1, nullptr, "Cancel"},
@@ -24,8 +25,7 @@ public:
{10400, nullptr, "GetBlockedUserListIds"},
{10500, nullptr, "GetProfileList"},
{10600, nullptr, "DeclareOpenOnlinePlaySession"},
- {10601, &IFriendService::DeclareCloseOnlinePlaySession,
- "DeclareCloseOnlinePlaySession"},
+ {10601, &IFriendService::DeclareCloseOnlinePlaySession, "DeclareCloseOnlinePlaySession"},
{10610, &IFriendService::UpdateUserPresence, "UpdateUserPresence"},
{10700, nullptr, "GetPlayHistoryRegistrationKey"},
{10701, nullptr, "GetPlayHistoryRegistrationKeyWithNetworkServiceAccountId"},
@@ -88,6 +88,7 @@ public:
{30830, nullptr, "ClearPlayLog"},
{49900, nullptr, "DeleteNetworkServiceAccountCache"},
};
+ // clang-format on
RegisterHandlers(functions);
}
diff --git a/src/core/hle/service/hid/controllers/debug_pad.h b/src/core/hle/service/hid/controllers/debug_pad.h
index 929035034..e584b92ec 100644
--- a/src/core/hle/service/hid/controllers/debug_pad.h
+++ b/src/core/hle/service/hid/controllers/debug_pad.h
@@ -41,20 +41,20 @@ private:
struct PadState {
union {
u32_le raw{};
- BitField<0, 1, u32_le> a;
- BitField<1, 1, u32_le> b;
- BitField<2, 1, u32_le> x;
- BitField<3, 1, u32_le> y;
- BitField<4, 1, u32_le> l;
- BitField<5, 1, u32_le> r;
- BitField<6, 1, u32_le> zl;
- BitField<7, 1, u32_le> zr;
- BitField<8, 1, u32_le> plus;
- BitField<9, 1, u32_le> minus;
- BitField<10, 1, u32_le> d_left;
- BitField<11, 1, u32_le> d_up;
- BitField<12, 1, u32_le> d_right;
- BitField<13, 1, u32_le> d_down;
+ 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;
+ BitField<5, 1, u32> r;
+ BitField<6, 1, u32> zl;
+ BitField<7, 1, u32> zr;
+ BitField<8, 1, u32> plus;
+ BitField<9, 1, u32> minus;
+ BitField<10, 1, u32> d_left;
+ BitField<11, 1, u32> d_up;
+ BitField<12, 1, u32> d_right;
+ BitField<13, 1, u32> d_down;
};
};
static_assert(sizeof(PadState) == 0x4, "PadState is an invalid size");
@@ -62,7 +62,7 @@ private:
struct Attributes {
union {
u32_le raw{};
- BitField<0, 1, u32_le> connected;
+ BitField<0, 1, u32> connected;
};
};
static_assert(sizeof(Attributes) == 0x4, "Attributes 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 e7fc7a619..fdd6d79a2 100644
--- a/src/core/hle/service/hid/controllers/npad.cpp
+++ b/src/core/hle/service/hid/controllers/npad.cpp
@@ -170,7 +170,7 @@ void Controller_NPad::InitNewlyAddedControler(std::size_t controller_idx) {
void Controller_NPad::OnInit() {
auto& kernel = Core::System::GetInstance().Kernel();
styleset_changed_event = Kernel::WritableEvent::CreateEventPair(
- kernel, Kernel::ResetType::OneShot, "npad:NpadStyleSetChanged");
+ kernel, Kernel::ResetType::Automatic, "npad:NpadStyleSetChanged");
if (!IsControllerActivated()) {
return;
diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h
index 18c7a94e6..4ff50b3cd 100644
--- a/src/core/hle/service/hid/controllers/npad.h
+++ b/src/core/hle/service/hid/controllers/npad.h
@@ -39,13 +39,13 @@ public:
union {
u32_le raw{};
- BitField<0, 1, u32_le> pro_controller;
- BitField<1, 1, u32_le> handheld;
- BitField<2, 1, u32_le> joycon_dual;
- BitField<3, 1, u32_le> joycon_left;
- BitField<4, 1, u32_le> joycon_right;
+ 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;
- BitField<6, 1, u32_le> pokeball; // TODO(ogniK): Confirm when possible
+ BitField<6, 1, u32> pokeball; // TODO(ogniK): Confirm when possible
};
};
static_assert(sizeof(NPadType) == 4, "NPadType is an invalid size");
@@ -150,43 +150,43 @@ private:
union {
u64_le raw{};
// Button states
- BitField<0, 1, u64_le> a;
- BitField<1, 1, u64_le> b;
- BitField<2, 1, u64_le> x;
- BitField<3, 1, u64_le> y;
- BitField<4, 1, u64_le> l_stick;
- BitField<5, 1, u64_le> r_stick;
- BitField<6, 1, u64_le> l;
- BitField<7, 1, u64_le> r;
- BitField<8, 1, u64_le> zl;
- BitField<9, 1, u64_le> zr;
- BitField<10, 1, u64_le> plus;
- BitField<11, 1, u64_le> minus;
+ BitField<0, 1, u64> a;
+ BitField<1, 1, u64> b;
+ BitField<2, 1, u64> x;
+ BitField<3, 1, u64> y;
+ BitField<4, 1, u64> l_stick;
+ BitField<5, 1, u64> r_stick;
+ BitField<6, 1, u64> l;
+ BitField<7, 1, u64> r;
+ BitField<8, 1, u64> zl;
+ BitField<9, 1, u64> zr;
+ BitField<10, 1, u64> plus;
+ BitField<11, 1, u64> minus;
// D-Pad
- BitField<12, 1, u64_le> d_left;
- BitField<13, 1, u64_le> d_up;
- BitField<14, 1, u64_le> d_right;
- BitField<15, 1, u64_le> d_down;
+ BitField<12, 1, u64> d_left;
+ BitField<13, 1, u64> d_up;
+ BitField<14, 1, u64> d_right;
+ BitField<15, 1, u64> d_down;
// Left JoyStick
- BitField<16, 1, u64_le> l_stick_left;
- BitField<17, 1, u64_le> l_stick_up;
- BitField<18, 1, u64_le> l_stick_right;
- BitField<19, 1, u64_le> l_stick_down;
+ BitField<16, 1, u64> l_stick_left;
+ BitField<17, 1, u64> l_stick_up;
+ BitField<18, 1, u64> l_stick_right;
+ BitField<19, 1, u64> l_stick_down;
// Right JoyStick
- BitField<20, 1, u64_le> r_stick_left;
- BitField<21, 1, u64_le> r_stick_up;
- BitField<22, 1, u64_le> r_stick_right;
- BitField<23, 1, u64_le> r_stick_down;
+ BitField<20, 1, u64> r_stick_left;
+ BitField<21, 1, u64> r_stick_up;
+ BitField<22, 1, u64> r_stick_right;
+ BitField<23, 1, u64> r_stick_down;
// Not always active?
- BitField<24, 1, u64_le> left_sl;
- BitField<25, 1, u64_le> left_sr;
+ BitField<24, 1, u64> left_sl;
+ BitField<25, 1, u64> left_sr;
- BitField<26, 1, u64_le> right_sl;
- BitField<27, 1, u64_le> right_sr;
+ BitField<26, 1, u64> right_sl;
+ BitField<27, 1, u64> right_sr;
};
};
static_assert(sizeof(ControllerPadState) == 8, "ControllerPadState is an invalid size");
@@ -200,12 +200,12 @@ private:
struct ConnectionState {
union {
u32_le raw{};
- BitField<0, 1, u32_le> IsConnected;
- BitField<1, 1, u32_le> IsWired;
- BitField<2, 1, u32_le> IsLeftJoyConnected;
- BitField<3, 1, u32_le> IsLeftJoyWired;
- BitField<4, 1, u32_le> IsRightJoyConnected;
- BitField<5, 1, u32_le> IsRightJoyWired;
+ 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;
};
};
static_assert(sizeof(ConnectionState) == 4, "ConnectionState is an invalid size");
@@ -240,23 +240,23 @@ private:
struct NPadProperties {
union {
s64_le raw{};
- BitField<11, 1, s64_le> is_vertical;
- BitField<12, 1, s64_le> is_horizontal;
- BitField<13, 1, s64_le> use_plus;
- BitField<14, 1, s64_le> use_minus;
+ BitField<11, 1, s64> is_vertical;
+ BitField<12, 1, s64> is_horizontal;
+ BitField<13, 1, s64> use_plus;
+ BitField<14, 1, s64> use_minus;
};
};
struct NPadDevice {
union {
u32_le raw{};
- BitField<0, 1, s32_le> pro_controller;
- BitField<1, 1, s32_le> handheld;
- BitField<2, 1, s32_le> handheld_left;
- BitField<3, 1, s32_le> handheld_right;
- BitField<4, 1, s32_le> joycon_left;
- BitField<5, 1, s32_le> joycon_right;
- BitField<6, 1, s32_le> pokeball;
+ BitField<0, 1, s32> pro_controller;
+ BitField<1, 1, s32> handheld;
+ 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;
};
};
diff --git a/src/core/hle/service/hid/controllers/touchscreen.h b/src/core/hle/service/hid/controllers/touchscreen.h
index 012b6e0dd..76fc340e9 100644
--- a/src/core/hle/service/hid/controllers/touchscreen.h
+++ b/src/core/hle/service/hid/controllers/touchscreen.h
@@ -33,8 +33,8 @@ private:
struct Attributes {
union {
u32 raw{};
- BitField<0, 1, u32_le> start_touch;
- BitField<1, 1, u32_le> end_touch;
+ BitField<0, 1, u32> start_touch;
+ BitField<1, 1, u32> end_touch;
};
};
static_assert(sizeof(Attributes) == 0x4, "Attributes is an invalid size");
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index 8a6de83a2..a4ad95d96 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -36,9 +36,9 @@ namespace Service::HID {
// Updating period for each HID device.
// TODO(ogniK): Find actual polling rate of hid
-constexpr u64 pad_update_ticks = Core::Timing::BASE_CLOCK_RATE / 66;
-constexpr u64 accelerometer_update_ticks = Core::Timing::BASE_CLOCK_RATE / 100;
-constexpr u64 gyroscope_update_ticks = Core::Timing::BASE_CLOCK_RATE / 100;
+constexpr s64 pad_update_ticks = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 66);
+constexpr s64 accelerometer_update_ticks = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 100);
+constexpr s64 gyroscope_update_ticks = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 100);
constexpr std::size_t SHARED_MEMORY_SIZE = 0x40000;
IAppletResource::IAppletResource() : ServiceFramework("IAppletResource") {
@@ -75,7 +75,7 @@ IAppletResource::IAppletResource() : ServiceFramework("IAppletResource") {
// Register update callbacks
auto& core_timing = Core::System::GetInstance().CoreTiming();
pad_update_event =
- core_timing.RegisterEvent("HID::UpdatePadCallback", [this](u64 userdata, int cycles_late) {
+ core_timing.RegisterEvent("HID::UpdatePadCallback", [this](u64 userdata, s64 cycles_late) {
UpdateControllers(userdata, cycles_late);
});
@@ -106,7 +106,7 @@ void IAppletResource::GetSharedMemoryHandle(Kernel::HLERequestContext& ctx) {
rb.PushCopyObjects(shared_mem);
}
-void IAppletResource::UpdateControllers(u64 userdata, int cycles_late) {
+void IAppletResource::UpdateControllers(u64 userdata, s64 cycles_late) {
auto& core_timing = Core::System::GetInstance().CoreTiming();
const bool should_reload = Settings::values.is_device_reload_pending.exchange(false);
@@ -210,6 +210,7 @@ Hid::Hid() : ServiceFramework("hid") {
{131, nullptr, "IsUnintendedHomeButtonInputProtectionEnabled"},
{132, nullptr, "EnableUnintendedHomeButtonInputProtection"},
{133, nullptr, "SetNpadJoyAssignmentModeSingleWithDestination"},
+ {134, nullptr, "SetNpadAnalogStickUseCenterClamp"},
{200, &Hid::GetVibrationDeviceInfo, "GetVibrationDeviceInfo"},
{201, &Hid::SendVibrationValue, "SendVibrationValue"},
{202, &Hid::GetActualVibrationValue, "GetActualVibrationValue"},
@@ -221,6 +222,7 @@ Hid::Hid() : ServiceFramework("hid") {
{208, nullptr, "GetActualVibrationGcErmCommand"},
{209, &Hid::BeginPermitVibrationSession, "BeginPermitVibrationSession"},
{210, &Hid::EndPermitVibrationSession, "EndPermitVibrationSession"},
+ {211, nullptr, "IsVibrationDeviceMounted"},
{300, &Hid::ActivateConsoleSixAxisSensor, "ActivateConsoleSixAxisSensor"},
{301, &Hid::StartConsoleSixAxisSensor, "StartConsoleSixAxisSensor"},
{302, nullptr, "StopConsoleSixAxisSensor"},
@@ -265,6 +267,7 @@ Hid::Hid() : ServiceFramework("hid") {
{523, nullptr, "SetIsPalmaPairedConnectable"},
{524, nullptr, "PairPalma"},
{525, &Hid::SetPalmaBoostMode, "SetPalmaBoostMode"},
+ {526, nullptr, "CancelWritePalmaWaveEntry"},
{1000, nullptr, "SetNpadCommunicationMode"},
{1001, nullptr, "GetNpadCommunicationMode"},
};
@@ -797,12 +800,22 @@ public:
{232, nullptr, "EnableShipmentMode"},
{233, nullptr, "ClearPairingInfo"},
{234, nullptr, "GetUniquePadDeviceTypeSetInternal"},
+ {235, nullptr, "EnableAnalogStickPower"},
{301, nullptr, "GetAbstractedPadHandles"},
{302, nullptr, "GetAbstractedPadState"},
{303, nullptr, "GetAbstractedPadsState"},
{321, nullptr, "SetAutoPilotVirtualPadState"},
{322, nullptr, "UnsetAutoPilotVirtualPadState"},
{323, nullptr, "UnsetAllAutoPilotVirtualPadState"},
+ {324, nullptr, "AttachHdlsWorkBuffer"},
+ {325, nullptr, "ReleaseHdlsWorkBuffer"},
+ {326, nullptr, "DumpHdlsNpadAssignmentState"},
+ {327, nullptr, "DumpHdlsStates"},
+ {328, nullptr, "ApplyHdlsNpadAssignmentState"},
+ {329, nullptr, "ApplyHdlsStateList"},
+ {330, nullptr, "AttachHdlsVirtualDevice"},
+ {331, nullptr, "DetachHdlsVirtualDevice"},
+ {332, nullptr, "SetHdlsState"},
{350, nullptr, "AddRegisteredDevice"},
{400, nullptr, "DisableExternalMcuOnNxDevice"},
{401, nullptr, "DisableRailDeviceFiltering"},
@@ -825,6 +838,7 @@ public:
{131, nullptr, "ActivateSleepButton"},
{141, nullptr, "AcquireCaptureButtonEventHandle"},
{151, nullptr, "ActivateCaptureButton"},
+ {161, nullptr, "GetPlatformConfig"},
{210, nullptr, "AcquireNfcDeviceUpdateEventHandle"},
{211, nullptr, "GetNpadsWithNfc"},
{212, nullptr, "AcquireNfcActivateEventHandle"},
@@ -894,6 +908,7 @@ public:
{827, nullptr, "IsAnalogStickButtonPressed"},
{828, nullptr, "IsAnalogStickInReleasePosition"},
{829, nullptr, "IsAnalogStickInCircumference"},
+ {830, nullptr, "SetNotificationLedPattern"},
{850, nullptr, "IsUsbFullKeyControllerEnabled"},
{851, nullptr, "EnableUsbFullKeyController"},
{852, nullptr, "IsUsbConnected"},
diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h
index 7cc58db4c..d3660cad2 100644
--- a/src/core/hle/service/hid/hid.h
+++ b/src/core/hle/service/hid/hid.h
@@ -4,6 +4,9 @@
#pragma once
+#include "core/hle/service/hid/controllers/controller_base.h"
+#include "core/hle/service/service.h"
+
#include "controllers/controller_base.h"
#include "core/hle/service/service.h"
@@ -62,7 +65,7 @@ private:
}
void GetSharedMemoryHandle(Kernel::HLERequestContext& ctx);
- void UpdateControllers(u64 userdata, int cycles_late);
+ void UpdateControllers(u64 userdata, s64 cycles_late);
Kernel::SharedPtr<Kernel::SharedMemory> shared_mem;
diff --git a/src/core/hle/service/ldn/ldn.cpp b/src/core/hle/service/ldn/ldn.cpp
index e250595e3..ed5059047 100644
--- a/src/core/hle/service/ldn/ldn.cpp
+++ b/src/core/hle/service/ldn/ldn.cpp
@@ -52,9 +52,11 @@ public:
}
};
-class ILocalCommunicationService final : public ServiceFramework<ILocalCommunicationService> {
+class ISystemLocalCommunicationService final
+ : public ServiceFramework<ISystemLocalCommunicationService> {
public:
- explicit ILocalCommunicationService(const char* name) : ServiceFramework{name} {
+ explicit ISystemLocalCommunicationService()
+ : ServiceFramework{"ISystemLocalCommunicationService"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetState"},
@@ -84,6 +86,50 @@ public:
{304, nullptr, "Disconnect"},
{400, nullptr, "InitializeSystem"},
{401, nullptr, "FinalizeSystem"},
+ {402, nullptr, "SetOperationMode"},
+ {403, nullptr, "InitializeSystem2"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+};
+
+class IUserLocalCommunicationService final
+ : public ServiceFramework<IUserLocalCommunicationService> {
+public:
+ explicit IUserLocalCommunicationService() : ServiceFramework{"IUserLocalCommunicationService"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "GetState"},
+ {1, nullptr, "GetNetworkInfo"},
+ {2, nullptr, "GetIpv4Address"},
+ {3, nullptr, "GetDisconnectReason"},
+ {4, nullptr, "GetSecurityParameter"},
+ {5, nullptr, "GetNetworkConfig"},
+ {100, nullptr, "AttachStateChangeEvent"},
+ {101, nullptr, "GetNetworkInfoLatestUpdate"},
+ {102, nullptr, "Scan"},
+ {103, nullptr, "ScanPrivate"},
+ {104, nullptr, "SetWirelessControllerRestriction"},
+ {200, nullptr, "OpenAccessPoint"},
+ {201, nullptr, "CloseAccessPoint"},
+ {202, nullptr, "CreateNetwork"},
+ {203, nullptr, "CreateNetworkPrivate"},
+ {204, nullptr, "DestroyNetwork"},
+ {205, nullptr, "Reject"},
+ {206, nullptr, "SetAdvertiseData"},
+ {207, nullptr, "SetStationAcceptPolicy"},
+ {208, nullptr, "AddAcceptFilterEntry"},
+ {209, nullptr, "ClearAcceptFilter"},
+ {300, nullptr, "OpenStation"},
+ {301, nullptr, "CloseStation"},
+ {302, nullptr, "Connect"},
+ {303, nullptr, "ConnectPrivate"},
+ {304, nullptr, "Disconnect"},
+ {400, nullptr, "Initialize"},
+ {401, nullptr, "Finalize"},
+ {402, nullptr, "SetOperationMode"},
};
// clang-format on
@@ -108,7 +154,7 @@ public:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<ILocalCommunicationService>("ISystemLocalCommunicationService");
+ rb.PushIpcInterface<ISystemLocalCommunicationService>();
}
};
@@ -129,7 +175,7 @@ public:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<ILocalCommunicationService>("IUserLocalCommunicationService");
+ rb.PushIpcInterface<IUserLocalCommunicationService>();
}
};
diff --git a/src/core/hle/service/ldr/ldr.cpp b/src/core/hle/service/ldr/ldr.cpp
index 9df7ac50f..b839303ac 100644
--- a/src/core/hle/service/ldr/ldr.cpp
+++ b/src/core/hle/service/ldr/ldr.cpp
@@ -86,6 +86,7 @@ public:
{2, &RelocatableObject::LoadNrr, "LoadNrr"},
{3, &RelocatableObject::UnloadNrr, "UnloadNrr"},
{4, &RelocatableObject::Initialize, "Initialize"},
+ {10, nullptr, "LoadNrrEx"},
};
// clang-format on
@@ -93,12 +94,18 @@ public:
}
void LoadNrr(Kernel::HLERequestContext& ctx) {
+ struct Parameters {
+ u64_le process_id;
+ u64_le nrr_address;
+ u64_le nrr_size;
+ };
+
IPC::RequestParser rp{ctx};
- rp.Skip(2, false);
- const VAddr nrr_addr{rp.Pop<VAddr>()};
- const u64 nrr_size{rp.Pop<u64>()};
- LOG_DEBUG(Service_LDR, "called with nrr_addr={:016X}, nrr_size={:016X}", nrr_addr,
- nrr_size);
+ const auto [process_id, nrr_address, nrr_size] = rp.PopRaw<Parameters>();
+
+ LOG_DEBUG(Service_LDR,
+ "called with process_id={:016X}, nrr_address={:016X}, nrr_size={:016X}",
+ process_id, nrr_address, nrr_size);
if (!initialized) {
LOG_ERROR(Service_LDR, "LDR:RO not initialized before use!");
@@ -116,24 +123,26 @@ public:
}
// NRR Address does not fall on 0x1000 byte boundary
- if (!Common::Is4KBAligned(nrr_addr)) {
- LOG_ERROR(Service_LDR, "NRR Address has invalid alignment (actual {:016X})!", nrr_addr);
+ if (!Common::Is4KBAligned(nrr_address)) {
+ LOG_ERROR(Service_LDR, "NRR Address has invalid alignment (actual {:016X})!",
+ nrr_address);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERROR_INVALID_ALIGNMENT);
return;
}
// NRR Size is zero or causes overflow
- if (nrr_addr + nrr_size <= nrr_addr || nrr_size == 0 || !Common::Is4KBAligned(nrr_size)) {
+ if (nrr_address + nrr_size <= nrr_address || nrr_size == 0 ||
+ !Common::Is4KBAligned(nrr_size)) {
LOG_ERROR(Service_LDR, "NRR Size is invalid! (nrr_address={:016X}, nrr_size={:016X})",
- nrr_addr, nrr_size);
+ nrr_address, nrr_size);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERROR_INVALID_SIZE);
return;
}
// Read NRR data from memory
std::vector<u8> nrr_data(nrr_size);
- Memory::ReadBlock(nrr_addr, nrr_data.data(), nrr_size);
+ Memory::ReadBlock(nrr_address, nrr_data.data(), nrr_size);
NRRHeader header;
std::memcpy(&header, nrr_data.data(), sizeof(NRRHeader));
@@ -174,7 +183,7 @@ public:
hashes.emplace_back(hash);
}
- nrr.insert_or_assign(nrr_addr, std::move(hashes));
+ nrr.insert_or_assign(nrr_address, std::move(hashes));
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -188,23 +197,30 @@ public:
return;
}
+ struct Parameters {
+ u64_le process_id;
+ u64_le nrr_address;
+ };
+
IPC::RequestParser rp{ctx};
- rp.Skip(2, false);
- const auto nrr_addr{rp.Pop<VAddr>()};
- LOG_DEBUG(Service_LDR, "called with nrr_addr={:016X}", nrr_addr);
+ const auto [process_id, nrr_address] = rp.PopRaw<Parameters>();
+
+ LOG_DEBUG(Service_LDR, "called with process_id={:016X}, nrr_addr={:016X}", process_id,
+ nrr_address);
- if (!Common::Is4KBAligned(nrr_addr)) {
- LOG_ERROR(Service_LDR, "NRR Address has invalid alignment (actual {:016X})!", nrr_addr);
+ if (!Common::Is4KBAligned(nrr_address)) {
+ LOG_ERROR(Service_LDR, "NRR Address has invalid alignment (actual {:016X})!",
+ nrr_address);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERROR_INVALID_ALIGNMENT);
return;
}
- const auto iter = nrr.find(nrr_addr);
+ const auto iter = nrr.find(nrr_address);
if (iter == nrr.end()) {
LOG_ERROR(Service_LDR,
"Attempting to unload NRR which has not been loaded! (addr={:016X})",
- nrr_addr);
+ nrr_address);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERROR_INVALID_NRR_ADDRESS);
return;
@@ -216,16 +232,22 @@ public:
}
void LoadNro(Kernel::HLERequestContext& ctx) {
+ struct Parameters {
+ u64_le process_id;
+ u64_le image_address;
+ u64_le image_size;
+ u64_le bss_address;
+ u64_le bss_size;
+ };
+
IPC::RequestParser rp{ctx};
- rp.Skip(2, false);
- const VAddr nro_addr{rp.Pop<VAddr>()};
- const u64 nro_size{rp.Pop<u64>()};
- const VAddr bss_addr{rp.Pop<VAddr>()};
- const u64 bss_size{rp.Pop<u64>()};
- LOG_DEBUG(
- Service_LDR,
- "called with nro_addr={:016X}, nro_size={:016X}, bss_addr={:016X}, bss_size={:016X}",
- nro_addr, nro_size, bss_addr, bss_size);
+ const auto [process_id, nro_address, nro_size, bss_address, bss_size] =
+ rp.PopRaw<Parameters>();
+
+ LOG_DEBUG(Service_LDR,
+ "called with pid={:016X}, nro_addr={:016X}, nro_size={:016X}, bss_addr={:016X}, "
+ "bss_size={:016X}",
+ process_id, nro_address, nro_size, bss_address, bss_size);
if (!initialized) {
LOG_ERROR(Service_LDR, "LDR:RO not initialized before use!");
@@ -243,8 +265,9 @@ public:
}
// NRO Address does not fall on 0x1000 byte boundary
- if (!Common::Is4KBAligned(nro_addr)) {
- LOG_ERROR(Service_LDR, "NRO Address has invalid alignment (actual {:016X})!", nro_addr);
+ if (!Common::Is4KBAligned(nro_address)) {
+ LOG_ERROR(Service_LDR, "NRO Address has invalid alignment (actual {:016X})!",
+ nro_address);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERROR_INVALID_ALIGNMENT);
return;
@@ -252,15 +275,15 @@ public:
// NRO Size or BSS Size is zero or causes overflow
const auto nro_size_valid =
- nro_size != 0 && nro_addr + nro_size > nro_addr && Common::Is4KBAligned(nro_size);
- const auto bss_size_valid =
- nro_size + bss_size >= nro_size && (bss_size == 0 || bss_addr + bss_size > bss_addr);
+ nro_size != 0 && nro_address + nro_size > nro_address && Common::Is4KBAligned(nro_size);
+ const auto bss_size_valid = nro_size + bss_size >= nro_size &&
+ (bss_size == 0 || bss_address + bss_size > bss_address);
if (!nro_size_valid || !bss_size_valid) {
LOG_ERROR(Service_LDR,
"NRO Size or BSS Size is invalid! (nro_address={:016X}, nro_size={:016X}, "
"bss_address={:016X}, bss_size={:016X})",
- nro_addr, nro_size, bss_addr, bss_size);
+ nro_address, nro_size, bss_address, bss_size);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERROR_INVALID_SIZE);
return;
@@ -268,7 +291,7 @@ public:
// Read NRO data from memory
std::vector<u8> nro_data(nro_size);
- Memory::ReadBlock(nro_addr, nro_data.data(), nro_size);
+ Memory::ReadBlock(nro_address, nro_data.data(), nro_size);
SHA256Hash hash{};
mbedtls_sha256(nro_data.data(), nro_data.size(), hash.data(), 0);
@@ -287,7 +310,7 @@ public:
if (!IsValidNROHash(hash)) {
LOG_ERROR(Service_LDR,
"NRO hash is not present in any currently loaded NRRs (hash={})!",
- Common::HexArrayToString(hash));
+ Common::HexToString(hash));
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERROR_MISSING_NRR_HASH);
return;
@@ -318,18 +341,18 @@ public:
return;
}
- ASSERT(vm_manager
- .MirrorMemory(*map_address, nro_addr, nro_size,
- Kernel::MemoryState::ModuleCodeStatic)
- .IsSuccess());
- ASSERT(vm_manager.UnmapRange(nro_addr, nro_size).IsSuccess());
+ ASSERT(
+ vm_manager
+ .MirrorMemory(*map_address, nro_address, nro_size, Kernel::MemoryState::ModuleCode)
+ .IsSuccess());
+ ASSERT(vm_manager.UnmapRange(nro_address, nro_size).IsSuccess());
if (bss_size > 0) {
ASSERT(vm_manager
- .MirrorMemory(*map_address + nro_size, bss_addr, bss_size,
- Kernel::MemoryState::ModuleCodeStatic)
+ .MirrorMemory(*map_address + nro_size, bss_address, bss_size,
+ Kernel::MemoryState::ModuleCode)
.IsSuccess());
- ASSERT(vm_manager.UnmapRange(bss_addr, bss_size).IsSuccess());
+ ASSERT(vm_manager.UnmapRange(bss_address, bss_size).IsSuccess());
}
vm_manager.ReprotectRange(*map_address, header.text_size,
@@ -349,13 +372,6 @@ public:
}
void UnloadNro(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- rp.Skip(2, false);
- const VAddr mapped_addr{rp.PopRaw<VAddr>()};
- const VAddr heap_addr{rp.PopRaw<VAddr>()};
- LOG_DEBUG(Service_LDR, "called with mapped_addr={:016X}, heap_addr={:016X}", mapped_addr,
- heap_addr);
-
if (!initialized) {
LOG_ERROR(Service_LDR, "LDR:RO not initialized before use!");
IPC::ResponseBuilder rb{ctx, 2};
@@ -363,22 +379,30 @@ public:
return;
}
- if (!Common::Is4KBAligned(mapped_addr) || !Common::Is4KBAligned(heap_addr)) {
- LOG_ERROR(Service_LDR,
- "NRO/BSS Address has invalid alignment (actual nro_addr={:016X}, "
- "bss_addr={:016X})!",
- mapped_addr, heap_addr);
+ struct Parameters {
+ u64_le process_id;
+ u64_le nro_address;
+ };
+
+ IPC::RequestParser rp{ctx};
+ const auto [process_id, nro_address] = rp.PopRaw<Parameters>();
+ LOG_DEBUG(Service_LDR, "called with process_id={:016X}, nro_address=0x{:016X}", process_id,
+ nro_address);
+
+ if (!Common::Is4KBAligned(nro_address)) {
+ LOG_ERROR(Service_LDR, "NRO address has invalid alignment (nro_address=0x{:016X})",
+ nro_address);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERROR_INVALID_ALIGNMENT);
return;
}
- const auto iter = nro.find(mapped_addr);
+ const auto iter = nro.find(nro_address);
if (iter == nro.end()) {
LOG_ERROR(Service_LDR,
- "The NRO attempting to unmap was not mapped or has an invalid address "
- "(actual {:016X})!",
- mapped_addr);
+ "The NRO attempting to be unmapped was not mapped or has an invalid address "
+ "(nro_address=0x{:016X})!",
+ nro_address);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERROR_INVALID_NRO_ADDRESS);
return;
@@ -387,11 +411,7 @@ public:
auto& vm_manager = Core::CurrentProcess()->VMManager();
const auto& nro_size = iter->second.size;
- ASSERT(vm_manager
- .MirrorMemory(heap_addr, mapped_addr, nro_size,
- Kernel::MemoryState::ModuleCodeStatic)
- .IsSuccess());
- ASSERT(vm_manager.UnmapRange(mapped_addr, nro_size).IsSuccess());
+ ASSERT(vm_manager.UnmapRange(nro_address, nro_size).IsSuccess());
Core::System::GetInstance().InvalidateCpuInstructionCaches();
@@ -461,11 +481,10 @@ private:
std::map<VAddr, NROInfo> nro;
std::map<VAddr, std::vector<SHA256Hash>> nrr;
- bool IsValidNROHash(const SHA256Hash& hash) {
- return std::any_of(
- nrr.begin(), nrr.end(), [&hash](const std::pair<VAddr, std::vector<SHA256Hash>>& p) {
- return std::find(p.second.begin(), p.second.end(), hash) != p.second.end();
- });
+ bool IsValidNROHash(const SHA256Hash& hash) const {
+ return std::any_of(nrr.begin(), nrr.end(), [&hash](const auto& p) {
+ return std::find(p.second.begin(), p.second.end(), hash) != p.second.end();
+ });
}
static bool IsValidNRO(const NROHeader& header, u64 nro_size, u64 bss_size) {
diff --git a/src/core/hle/service/lm/lm.cpp b/src/core/hle/service/lm/lm.cpp
index 1f462e087..2a61593e2 100644
--- a/src/core/hle/service/lm/lm.cpp
+++ b/src/core/hle/service/lm/lm.cpp
@@ -42,7 +42,7 @@ private:
union {
BitField<0, 16, Flags> flags;
BitField<16, 8, Severity> severity;
- BitField<24, 8, u32_le> verbosity;
+ BitField<24, 8, u32> verbosity;
};
u32_le payload_size;
diff --git a/src/core/hle/service/mii/mii.cpp b/src/core/hle/service/mii/mii.cpp
index a6197124a..ce84e25ed 100644
--- a/src/core/hle/service/mii/mii.cpp
+++ b/src/core/hle/service/mii/mii.cpp
@@ -4,42 +4,50 @@
#include <memory>
+#include <fmt/ostream.h>
+
#include "common/logging/log.h"
+#include "common/string_util.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/service/mii/mii.h"
+#include "core/hle/service/mii/mii_manager.h"
#include "core/hle/service/service.h"
#include "core/hle/service/sm/sm.h"
namespace Service::Mii {
+constexpr ResultCode ERROR_INVALID_ARGUMENT{ErrorModule::Mii, 1};
+constexpr ResultCode ERROR_CANNOT_FIND_ENTRY{ErrorModule::Mii, 4};
+constexpr ResultCode ERROR_NOT_IN_TEST_MODE{ErrorModule::Mii, 99};
+
class IDatabaseService final : public ServiceFramework<IDatabaseService> {
public:
explicit IDatabaseService() : ServiceFramework{"IDatabaseService"} {
// clang-format off
static const FunctionInfo functions[] = {
- {0, nullptr, "IsUpdated"},
- {1, nullptr, "IsFullDatabase"},
- {2, nullptr, "GetCount"},
- {3, nullptr, "Get"},
- {4, nullptr, "Get1"},
+ {0, &IDatabaseService::IsUpdated, "IsUpdated"},
+ {1, &IDatabaseService::IsFullDatabase, "IsFullDatabase"},
+ {2, &IDatabaseService::GetCount, "GetCount"},
+ {3, &IDatabaseService::Get, "Get"},
+ {4, &IDatabaseService::Get1, "Get1"},
{5, nullptr, "UpdateLatest"},
- {6, nullptr, "BuildRandom"},
- {7, nullptr, "BuildDefault"},
- {8, nullptr, "Get2"},
- {9, nullptr, "Get3"},
+ {6, &IDatabaseService::BuildRandom, "BuildRandom"},
+ {7, &IDatabaseService::BuildDefault, "BuildDefault"},
+ {8, &IDatabaseService::Get2, "Get2"},
+ {9, &IDatabaseService::Get3, "Get3"},
{10, nullptr, "UpdateLatest1"},
- {11, nullptr, "FindIndex"},
- {12, nullptr, "Move"},
- {13, nullptr, "AddOrReplace"},
- {14, nullptr, "Delete"},
- {15, nullptr, "DestroyFile"},
- {16, nullptr, "DeleteFile"},
- {17, nullptr, "Format"},
+ {11, &IDatabaseService::FindIndex, "FindIndex"},
+ {12, &IDatabaseService::Move, "Move"},
+ {13, &IDatabaseService::AddOrReplace, "AddOrReplace"},
+ {14, &IDatabaseService::Delete, "Delete"},
+ {15, &IDatabaseService::DestroyFile, "DestroyFile"},
+ {16, &IDatabaseService::DeleteFile, "DeleteFile"},
+ {17, &IDatabaseService::Format, "Format"},
{18, nullptr, "Import"},
{19, nullptr, "Export"},
{20, nullptr, "IsBrokenDatabaseWithClearFlag"},
- {21, nullptr, "GetIndex"},
+ {21, &IDatabaseService::GetIndex, "GetIndex"},
{22, nullptr, "SetInterfaceVersion"},
{23, nullptr, "Convert"},
};
@@ -47,6 +55,305 @@ public:
RegisterHandlers(functions);
}
+
+private:
+ template <typename OutType>
+ std::vector<u8> SerializeArray(OutType (MiiManager::*getter)(u32) const, u32 offset,
+ u32 requested_size, u32& read_size) {
+ read_size = std::min(requested_size, db.Size() - offset);
+
+ std::vector<u8> out(read_size * sizeof(OutType));
+
+ for (u32 i = 0; i < read_size; ++i) {
+ const auto obj = (db.*getter)(offset + i);
+ std::memcpy(out.data() + i * sizeof(OutType), &obj, sizeof(OutType));
+ }
+
+ return out;
+ }
+
+ void IsUpdated(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto source{rp.PopRaw<Source>()};
+
+ LOG_DEBUG(Service_Mii, "called with source={}", source);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(db.CheckUpdatedFlag());
+ db.ResetUpdatedFlag();
+ }
+
+ void IsFullDatabase(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Mii, "called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(db.Full());
+ }
+
+ void GetCount(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto source{rp.PopRaw<Source>()};
+
+ LOG_DEBUG(Service_Mii, "called with source={}", source);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(db.Size());
+ }
+
+ // Gets Miis from database at offset and index in format MiiInfoElement
+ void Get(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto size{rp.PopRaw<u32>()};
+ const auto source{rp.PopRaw<Source>()};
+
+ LOG_DEBUG(Service_Mii, "called with size={:08X}, offset={:08X}, source={}", size,
+ offsets[0], source);
+
+ u32 read_size{};
+ ctx.WriteBuffer(SerializeArray(&MiiManager::GetInfoElement, offsets[0], size, read_size));
+ offsets[0] += read_size;
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(read_size);
+ }
+
+ // Gets Miis from database at offset and index in format MiiInfo
+ void Get1(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto size{rp.PopRaw<u32>()};
+ const auto source{rp.PopRaw<Source>()};
+
+ LOG_DEBUG(Service_Mii, "called with size={:08X}, offset={:08X}, source={}", size,
+ offsets[1], source);
+
+ u32 read_size{};
+ ctx.WriteBuffer(SerializeArray(&MiiManager::GetInfo, offsets[1], size, read_size));
+ offsets[1] += read_size;
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(read_size);
+ }
+
+ void BuildRandom(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto [unknown1, unknown2, unknown3] = rp.PopRaw<RandomParameters>();
+
+ if (unknown1 > 3) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERROR_INVALID_ARGUMENT);
+ LOG_ERROR(Service_Mii, "Invalid unknown1 value: {}", unknown1);
+ return;
+ }
+
+ if (unknown2 > 2) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERROR_INVALID_ARGUMENT);
+ LOG_ERROR(Service_Mii, "Invalid unknown2 value: {}", unknown2);
+ return;
+ }
+
+ if (unknown3 > 3) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERROR_INVALID_ARGUMENT);
+ LOG_ERROR(Service_Mii, "Invalid unknown3 value: {}", unknown3);
+ return;
+ }
+
+ LOG_DEBUG(Service_Mii, "called with param_1={:08X}, param_2={:08X}, param_3={:08X}",
+ unknown1, unknown2, unknown3);
+
+ const auto info = db.CreateRandom({unknown1, unknown2, unknown3});
+ IPC::ResponseBuilder rb{ctx, 2 + sizeof(MiiInfo) / sizeof(u32)};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushRaw<MiiInfo>(info);
+ }
+
+ void BuildDefault(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto index{rp.PopRaw<u32>()};
+
+ if (index > 5) {
+ LOG_ERROR(Service_Mii, "invalid argument, index cannot be greater than 5 but is {:08X}",
+ index);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERROR_INVALID_ARGUMENT);
+ return;
+ }
+
+ LOG_DEBUG(Service_Mii, "called with index={:08X}", index);
+
+ const auto info = db.CreateDefault(index);
+ IPC::ResponseBuilder rb{ctx, 2 + sizeof(MiiInfo) / sizeof(u32)};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushRaw<MiiInfo>(info);
+ }
+
+ // Gets Miis from database at offset and index in format MiiStoreDataElement
+ void Get2(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto size{rp.PopRaw<u32>()};
+ const auto source{rp.PopRaw<Source>()};
+
+ LOG_DEBUG(Service_Mii, "called with size={:08X}, offset={:08X}, source={}", size,
+ offsets[2], source);
+
+ u32 read_size{};
+ ctx.WriteBuffer(
+ SerializeArray(&MiiManager::GetStoreDataElement, offsets[2], size, read_size));
+ offsets[2] += read_size;
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(read_size);
+ }
+
+ // Gets Miis from database at offset and index in format MiiStoreData
+ void Get3(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto size{rp.PopRaw<u32>()};
+ const auto source{rp.PopRaw<Source>()};
+
+ LOG_DEBUG(Service_Mii, "called with size={:08X}, offset={:08X}, source={}", size,
+ offsets[3], source);
+
+ u32 read_size{};
+ ctx.WriteBuffer(SerializeArray(&MiiManager::GetStoreData, offsets[3], size, read_size));
+ offsets[3] += read_size;
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(read_size);
+ }
+
+ void FindIndex(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto uuid{rp.PopRaw<Common::UUID>()};
+ const auto unknown{rp.PopRaw<bool>()};
+
+ LOG_DEBUG(Service_Mii, "called with uuid={}, unknown={}", uuid.FormatSwitch(), unknown);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+
+ const auto index = db.IndexOf(uuid);
+ if (index > MAX_MIIS) {
+ // TODO(DarkLordZach): Find a better error code
+ rb.Push(ResultCode(-1));
+ rb.Push(index);
+ } else {
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(index);
+ }
+ }
+
+ void Move(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto uuid{rp.PopRaw<Common::UUID>()};
+ const auto index{rp.PopRaw<s32>()};
+
+ if (index < 0) {
+ LOG_ERROR(Service_Mii, "Index cannot be negative but is {:08X}!", index);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERROR_INVALID_ARGUMENT);
+ return;
+ }
+
+ LOG_DEBUG(Service_Mii, "called with uuid={}, index={:08X}", uuid.FormatSwitch(), index);
+
+ const auto success = db.Move(uuid, index);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ // TODO(DarkLordZach): Find a better error code
+ rb.Push(success ? RESULT_SUCCESS : ResultCode(-1));
+ }
+
+ void AddOrReplace(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto data{rp.PopRaw<MiiStoreData>()};
+
+ LOG_DEBUG(Service_Mii, "called with Mii data uuid={}, name={}", data.uuid.FormatSwitch(),
+ Common::UTF16ToUTF8(data.Name()));
+
+ const auto success = db.AddOrReplace(data);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ // TODO(DarkLordZach): Find a better error code
+ rb.Push(success ? RESULT_SUCCESS : ResultCode(-1));
+ }
+
+ void Delete(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto uuid{rp.PopRaw<Common::UUID>()};
+
+ LOG_DEBUG(Service_Mii, "called with uuid={}", uuid.FormatSwitch());
+
+ const auto success = db.Remove(uuid);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(success ? RESULT_SUCCESS : ERROR_CANNOT_FIND_ENTRY);
+ }
+
+ void DestroyFile(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Mii, "called");
+
+ if (!db.IsTestModeEnabled()) {
+ LOG_ERROR(Service_Mii, "Database is not in test mode -- cannot destory database file.");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERROR_NOT_IN_TEST_MODE);
+ return;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(db.DestroyFile());
+ }
+
+ void DeleteFile(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Mii, "called");
+
+ if (!db.IsTestModeEnabled()) {
+ LOG_ERROR(Service_Mii, "Database is not in test mode -- cannot delete database file.");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERROR_NOT_IN_TEST_MODE);
+ return;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(db.DeleteFile());
+ }
+
+ void Format(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Mii, "called");
+
+ db.Clear();
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void GetIndex(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto info{rp.PopRaw<MiiInfo>()};
+
+ LOG_DEBUG(Service_Mii, "called with Mii info uuid={}, name={}", info.uuid.FormatSwitch(),
+ Common::UTF16ToUTF8(info.Name()));
+
+ const auto index = db.IndexOf(info);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(index);
+ }
+
+ MiiManager db;
+
+ // Last read offsets of Get functions
+ std::array<u32, 4> offsets{};
};
class MiiDBModule final : public ServiceFramework<MiiDBModule> {
diff --git a/src/core/hle/service/mii/mii_manager.cpp b/src/core/hle/service/mii/mii_manager.cpp
new file mode 100644
index 000000000..131b01d62
--- /dev/null
+++ b/src/core/hle/service/mii/mii_manager.cpp
@@ -0,0 +1,416 @@
+// Copyright 2018 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/file_util.h"
+#include "common/logging/log.h"
+#include "common/string_util.h"
+#include "core/hle/service/mii/mii_manager.h"
+
+namespace Service::Mii {
+
+namespace {
+
+constexpr char MII_SAVE_DATABASE_PATH[] = "/system/save/8000000000000030/MiiDatabase.dat";
+constexpr std::array<char16_t, 11> DEFAULT_MII_NAME = {u'y', u'u', u'z', u'u', u'\0'};
+
+// This value was retrieved from HW test
+constexpr MiiStoreData DEFAULT_MII = {
+ {
+ 0x21, 0x40, 0x40, 0x01, 0x08, 0x01, 0x13, 0x08, 0x08, 0x02, 0x17, 0x8C, 0x06, 0x01,
+ 0x69, 0x6D, 0x8A, 0x6A, 0x82, 0x14, 0x00, 0x00, 0x00, 0x20, 0x64, 0x72, 0x44, 0x44,
+ },
+ {'y', 'u', 'z', 'u', '\0'},
+ Common::UUID{1, 0},
+ 0,
+ 0,
+};
+
+// Default values taken from multiple real databases
+const MiiDatabase DEFAULT_MII_DATABASE{Common::MakeMagic('N', 'F', 'D', 'B'), {}, {1}, 0, 0};
+
+constexpr std::array<const char*, 4> SOURCE_NAMES{
+ "Database",
+ "Default",
+ "Account",
+ "Friend",
+};
+
+template <typename T, std::size_t SourceArraySize, std::size_t DestArraySize>
+std::array<T, DestArraySize> ResizeArray(const std::array<T, SourceArraySize>& in) {
+ std::array<T, DestArraySize> out{};
+ std::memcpy(out.data(), in.data(), sizeof(T) * std::min(SourceArraySize, DestArraySize));
+ return out;
+}
+
+MiiInfo ConvertStoreDataToInfo(const MiiStoreData& data) {
+ MiiStoreBitFields bf{};
+ std::memcpy(&bf, data.data.data(), sizeof(MiiStoreBitFields));
+ return {
+ data.uuid,
+ ResizeArray<char16_t, 10, 11>(data.name),
+ static_cast<u8>(bf.font_region.Value()),
+ static_cast<u8>(bf.favorite_color.Value()),
+ static_cast<u8>(bf.gender.Value()),
+ static_cast<u8>(bf.height.Value()),
+ static_cast<u8>(bf.weight.Value()),
+ static_cast<u8>(bf.mii_type.Value()),
+ static_cast<u8>(bf.mii_region.Value()),
+ static_cast<u8>(bf.face_type.Value()),
+ static_cast<u8>(bf.face_color.Value()),
+ static_cast<u8>(bf.face_wrinkle.Value()),
+ static_cast<u8>(bf.face_makeup.Value()),
+ static_cast<u8>(bf.hair_type.Value()),
+ static_cast<u8>(bf.hair_color.Value()),
+ static_cast<bool>(bf.hair_flip.Value()),
+ static_cast<u8>(bf.eye_type.Value()),
+ static_cast<u8>(bf.eye_color.Value()),
+ static_cast<u8>(bf.eye_scale.Value()),
+ static_cast<u8>(bf.eye_aspect.Value()),
+ static_cast<u8>(bf.eye_rotate.Value()),
+ static_cast<u8>(bf.eye_x.Value()),
+ static_cast<u8>(bf.eye_y.Value()),
+ static_cast<u8>(bf.eyebrow_type.Value()),
+ static_cast<u8>(bf.eyebrow_color.Value()),
+ static_cast<u8>(bf.eyebrow_scale.Value()),
+ static_cast<u8>(bf.eyebrow_aspect.Value()),
+ static_cast<u8>(bf.eyebrow_rotate.Value()),
+ static_cast<u8>(bf.eyebrow_x.Value()),
+ static_cast<u8>(bf.eyebrow_y.Value()),
+ static_cast<u8>(bf.nose_type.Value()),
+ static_cast<u8>(bf.nose_scale.Value()),
+ static_cast<u8>(bf.nose_y.Value()),
+ static_cast<u8>(bf.mouth_type.Value()),
+ static_cast<u8>(bf.mouth_color.Value()),
+ static_cast<u8>(bf.mouth_scale.Value()),
+ static_cast<u8>(bf.mouth_aspect.Value()),
+ static_cast<u8>(bf.mouth_y.Value()),
+ static_cast<u8>(bf.facial_hair_color.Value()),
+ static_cast<u8>(bf.beard_type.Value()),
+ static_cast<u8>(bf.mustache_type.Value()),
+ static_cast<u8>(bf.mustache_scale.Value()),
+ static_cast<u8>(bf.mustache_y.Value()),
+ static_cast<u8>(bf.glasses_type.Value()),
+ static_cast<u8>(bf.glasses_color.Value()),
+ static_cast<u8>(bf.glasses_scale.Value()),
+ static_cast<u8>(bf.glasses_y.Value()),
+ static_cast<u8>(bf.mole_type.Value()),
+ static_cast<u8>(bf.mole_scale.Value()),
+ static_cast<u8>(bf.mole_x.Value()),
+ static_cast<u8>(bf.mole_y.Value()),
+ 0x00,
+ };
+}
+MiiStoreData ConvertInfoToStoreData(const MiiInfo& info) {
+ MiiStoreData out{};
+ out.name = ResizeArray<char16_t, 11, 10>(info.name);
+ out.uuid = info.uuid;
+
+ MiiStoreBitFields bf{};
+
+ bf.hair_type.Assign(info.hair_type);
+ bf.mole_type.Assign(info.mole_type);
+ bf.height.Assign(info.height);
+ bf.hair_flip.Assign(info.hair_flip);
+ bf.weight.Assign(info.weight);
+ bf.hair_color.Assign(info.hair_color);
+
+ bf.gender.Assign(info.gender);
+ bf.eye_color.Assign(info.eye_color);
+ bf.eyebrow_color.Assign(info.eyebrow_color);
+ bf.mouth_color.Assign(info.mouth_color);
+ bf.facial_hair_color.Assign(info.facial_hair_color);
+
+ bf.mii_type.Assign(info.mii_type);
+ bf.glasses_color.Assign(info.glasses_color);
+ bf.font_region.Assign(info.font_region);
+ bf.eye_type.Assign(info.eye_type);
+ bf.mii_region.Assign(info.mii_region);
+ bf.mouth_type.Assign(info.mouth_type);
+ bf.glasses_scale.Assign(info.glasses_scale);
+ bf.eye_y.Assign(info.eye_y);
+
+ bf.mustache_type.Assign(info.mustache_type);
+ bf.eyebrow_type.Assign(info.eyebrow_type);
+ bf.beard_type.Assign(info.beard_type);
+ bf.nose_type.Assign(info.nose_type);
+ bf.mouth_aspect.Assign(info.mouth_aspect_ratio);
+ bf.nose_y.Assign(info.nose_y);
+ bf.eyebrow_aspect.Assign(info.eyebrow_aspect_ratio);
+ bf.mouth_y.Assign(info.mouth_y);
+
+ bf.eye_rotate.Assign(info.eye_rotate);
+ bf.mustache_y.Assign(info.mustache_y);
+ bf.eye_aspect.Assign(info.eye_aspect_ratio);
+ bf.glasses_y.Assign(info.glasses_y);
+ bf.eye_scale.Assign(info.eye_scale);
+ bf.mole_x.Assign(info.mole_x);
+ bf.mole_y.Assign(info.mole_y);
+
+ bf.glasses_type.Assign(info.glasses_type);
+ bf.face_type.Assign(info.face_type);
+ bf.favorite_color.Assign(info.favorite_color);
+ bf.face_wrinkle.Assign(info.face_wrinkle);
+ bf.face_color.Assign(info.face_color);
+ bf.eye_x.Assign(info.eye_x);
+ bf.face_makeup.Assign(info.face_makeup);
+
+ bf.eyebrow_rotate.Assign(info.eyebrow_rotate);
+ bf.eyebrow_scale.Assign(info.eyebrow_scale);
+ bf.eyebrow_y.Assign(info.eyebrow_y);
+ bf.eyebrow_x.Assign(info.eyebrow_x);
+ bf.mouth_scale.Assign(info.mouth_scale);
+ bf.nose_scale.Assign(info.nose_scale);
+ bf.mole_scale.Assign(info.mole_scale);
+ bf.mustache_scale.Assign(info.mustache_scale);
+
+ std::memcpy(out.data.data(), &bf, sizeof(MiiStoreBitFields));
+
+ return out;
+}
+
+} // namespace
+
+std::ostream& operator<<(std::ostream& os, Source source) {
+ os << SOURCE_NAMES.at(static_cast<std::size_t>(source));
+ return os;
+}
+
+std::u16string MiiInfo::Name() const {
+ return Common::UTF16StringFromFixedZeroTerminatedBuffer(name.data(), name.size());
+}
+
+bool operator==(const MiiInfo& lhs, const MiiInfo& rhs) {
+ return std::memcmp(&lhs, &rhs, sizeof(MiiInfo)) == 0;
+}
+
+bool operator!=(const MiiInfo& lhs, const MiiInfo& rhs) {
+ return !operator==(lhs, rhs);
+}
+
+std::u16string MiiStoreData::Name() const {
+ return Common::UTF16StringFromFixedZeroTerminatedBuffer(name.data(), name.size());
+}
+
+MiiManager::MiiManager() = default;
+
+MiiManager::~MiiManager() = default;
+
+MiiInfo MiiManager::CreateRandom(RandomParameters params) {
+ LOG_WARNING(Service_Mii,
+ "(STUBBED) called with params={:08X}{:08X}{:08X}, returning default Mii",
+ params.unknown_1, params.unknown_2, params.unknown_3);
+
+ return ConvertStoreDataToInfo(CreateMiiWithUniqueUUID());
+}
+
+MiiInfo MiiManager::CreateDefault(u32 index) {
+ const auto new_mii = CreateMiiWithUniqueUUID();
+
+ database.miis.at(index) = new_mii;
+
+ EnsureDatabasePartition();
+ return ConvertStoreDataToInfo(new_mii);
+}
+
+bool MiiManager::CheckUpdatedFlag() const {
+ return updated_flag;
+}
+
+void MiiManager::ResetUpdatedFlag() {
+ updated_flag = false;
+}
+
+bool MiiManager::IsTestModeEnabled() const {
+ return is_test_mode_enabled;
+}
+
+bool MiiManager::Empty() const {
+ return Size() == 0;
+}
+
+bool MiiManager::Full() const {
+ return Size() == MAX_MIIS;
+}
+
+void MiiManager::Clear() {
+ updated_flag = true;
+ std::fill(database.miis.begin(), database.miis.end(), MiiStoreData{});
+}
+
+u32 MiiManager::Size() const {
+ return static_cast<u32>(std::count_if(database.miis.begin(), database.miis.end(),
+ [](const MiiStoreData& elem) { return elem.uuid; }));
+}
+
+MiiInfo MiiManager::GetInfo(u32 index) const {
+ return ConvertStoreDataToInfo(GetStoreData(index));
+}
+
+MiiInfoElement MiiManager::GetInfoElement(u32 index) const {
+ return {GetInfo(index), Source::Database};
+}
+
+MiiStoreData MiiManager::GetStoreData(u32 index) const {
+ return database.miis.at(index);
+}
+
+MiiStoreDataElement MiiManager::GetStoreDataElement(u32 index) const {
+ return {GetStoreData(index), Source::Database};
+}
+
+bool MiiManager::Remove(Common::UUID uuid) {
+ const auto iter = std::find_if(database.miis.begin(), database.miis.end(),
+ [uuid](const MiiStoreData& elem) { return elem.uuid == uuid; });
+
+ if (iter == database.miis.end())
+ return false;
+
+ updated_flag = true;
+ *iter = MiiStoreData{};
+ EnsureDatabasePartition();
+ return true;
+}
+
+u32 MiiManager::IndexOf(Common::UUID uuid) const {
+ const auto iter = std::find_if(database.miis.begin(), database.miis.end(),
+ [uuid](const MiiStoreData& elem) { return elem.uuid == uuid; });
+
+ if (iter == database.miis.end())
+ return INVALID_INDEX;
+
+ return static_cast<u32>(std::distance(database.miis.begin(), iter));
+}
+
+u32 MiiManager::IndexOf(const MiiInfo& info) const {
+ const auto iter =
+ std::find_if(database.miis.begin(), database.miis.end(), [&info](const MiiStoreData& elem) {
+ return ConvertStoreDataToInfo(elem) == info;
+ });
+
+ if (iter == database.miis.end())
+ return INVALID_INDEX;
+
+ return static_cast<u32>(std::distance(database.miis.begin(), iter));
+}
+
+bool MiiManager::Move(Common::UUID uuid, u32 new_index) {
+ const auto index = IndexOf(uuid);
+
+ if (index == INVALID_INDEX || new_index >= MAX_MIIS)
+ return false;
+
+ updated_flag = true;
+ const auto moving = database.miis[index];
+ const auto replacing = database.miis[new_index];
+ if (replacing.uuid) {
+ database.miis[index] = replacing;
+ database.miis[new_index] = moving;
+ } else {
+ database.miis[index] = MiiStoreData{};
+ database.miis[new_index] = moving;
+ }
+
+ EnsureDatabasePartition();
+ return true;
+}
+
+bool MiiManager::AddOrReplace(const MiiStoreData& data) {
+ const auto index = IndexOf(data.uuid);
+
+ updated_flag = true;
+ if (index == INVALID_INDEX) {
+ const auto size = Size();
+ if (size == MAX_MIIS)
+ return false;
+ database.miis[size] = data;
+ } else {
+ database.miis[index] = data;
+ }
+
+ return true;
+}
+
+bool MiiManager::DestroyFile() {
+ database = DEFAULT_MII_DATABASE;
+ updated_flag = false;
+ return DeleteFile();
+}
+
+bool MiiManager::DeleteFile() {
+ const auto path = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + MII_SAVE_DATABASE_PATH;
+ return FileUtil::Exists(path) && FileUtil::Delete(path);
+}
+
+void MiiManager::WriteToFile() {
+ const auto raw_path =
+ FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "/system/save/8000000000000030";
+ if (FileUtil::Exists(raw_path) && !FileUtil::IsDirectory(raw_path))
+ FileUtil::Delete(raw_path);
+
+ const auto path = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + MII_SAVE_DATABASE_PATH;
+
+ if (!FileUtil::CreateFullPath(path)) {
+ LOG_WARNING(Service_Mii,
+ "Failed to create full path of MiiDatabase.dat. Create the directory "
+ "nand/system/save/8000000000000030 to mitigate this "
+ "issue.");
+ return;
+ }
+
+ FileUtil::IOFile save(path, "wb");
+
+ if (!save.IsOpen()) {
+ LOG_WARNING(Service_Mii, "Failed to write save data to file... No changes to user data "
+ "made in current session will be saved.");
+ return;
+ }
+
+ save.Resize(sizeof(MiiDatabase));
+ if (save.WriteBytes(&database, sizeof(MiiDatabase)) != sizeof(MiiDatabase)) {
+ LOG_WARNING(Service_Mii, "Failed to write all data to save file... Data may be malformed "
+ "and/or regenerated on next run.");
+ save.Resize(0);
+ }
+}
+
+void MiiManager::ReadFromFile() {
+ FileUtil::IOFile save(
+ FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + MII_SAVE_DATABASE_PATH, "rb");
+
+ if (!save.IsOpen()) {
+ LOG_WARNING(Service_ACC, "Failed to load profile data from save data... Generating new "
+ "blank Mii database with no Miis.");
+ std::memcpy(&database, &DEFAULT_MII_DATABASE, sizeof(MiiDatabase));
+ return;
+ }
+
+ if (save.ReadBytes(&database, sizeof(MiiDatabase)) != sizeof(MiiDatabase)) {
+ LOG_WARNING(Service_ACC, "MiiDatabase.dat is smaller than expected... Generating new blank "
+ "Mii database with no Miis.");
+ std::memcpy(&database, &DEFAULT_MII_DATABASE, sizeof(MiiDatabase));
+ return;
+ }
+
+ EnsureDatabasePartition();
+}
+
+MiiStoreData MiiManager::CreateMiiWithUniqueUUID() const {
+ auto new_mii = DEFAULT_MII;
+
+ do {
+ new_mii.uuid = Common::UUID::Generate();
+ } while (IndexOf(new_mii.uuid) != INVALID_INDEX);
+
+ return new_mii;
+}
+
+void MiiManager::EnsureDatabasePartition() {
+ std::stable_partition(database.miis.begin(), database.miis.end(),
+ [](const MiiStoreData& elem) { return elem.uuid; });
+}
+
+} // namespace Service::Mii
diff --git a/src/core/hle/service/mii/mii_manager.h b/src/core/hle/service/mii/mii_manager.h
new file mode 100644
index 000000000..38ad78a0d
--- /dev/null
+++ b/src/core/hle/service/mii/mii_manager.h
@@ -0,0 +1,273 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/bit_field.h"
+#include "common/common_funcs.h"
+#include "common/uuid.h"
+
+namespace Service::Mii {
+
+constexpr std::size_t MAX_MIIS = 100;
+constexpr u32 INVALID_INDEX = 0xFFFFFFFF;
+
+struct RandomParameters {
+ u32 unknown_1;
+ u32 unknown_2;
+ u32 unknown_3;
+};
+static_assert(sizeof(RandomParameters) == 0xC, "RandomParameters has incorrect size.");
+
+enum class Source : u32 {
+ Database = 0,
+ Default = 1,
+ Account = 2,
+ Friend = 3,
+};
+
+std::ostream& operator<<(std::ostream& os, Source source);
+
+struct MiiInfo {
+ Common::UUID uuid;
+ std::array<char16_t, 11> name;
+ u8 font_region;
+ u8 favorite_color;
+ u8 gender;
+ u8 height;
+ u8 weight;
+ u8 mii_type;
+ u8 mii_region;
+ u8 face_type;
+ u8 face_color;
+ u8 face_wrinkle;
+ u8 face_makeup;
+ u8 hair_type;
+ u8 hair_color;
+ bool hair_flip;
+ u8 eye_type;
+ u8 eye_color;
+ u8 eye_scale;
+ u8 eye_aspect_ratio;
+ u8 eye_rotate;
+ u8 eye_x;
+ u8 eye_y;
+ u8 eyebrow_type;
+ u8 eyebrow_color;
+ u8 eyebrow_scale;
+ u8 eyebrow_aspect_ratio;
+ 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_ratio;
+ u8 mouth_y;
+ u8 facial_hair_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);
+
+ std::u16string Name() const;
+};
+static_assert(sizeof(MiiInfo) == 0x58, "MiiInfo has incorrect size.");
+static_assert(std::has_unique_object_representations_v<MiiInfo>,
+ "All bits of MiiInfo must contribute to its value.");
+
+bool operator==(const MiiInfo& lhs, const MiiInfo& rhs);
+bool operator!=(const MiiInfo& lhs, const MiiInfo& rhs);
+
+#pragma pack(push, 4)
+struct MiiInfoElement {
+ MiiInfo info;
+ Source source;
+};
+static_assert(sizeof(MiiInfoElement) == 0x5C, "MiiInfoElement has incorrect size.");
+
+struct MiiStoreBitFields {
+ union {
+ u32 word_0;
+
+ BitField<24, 8, u32> hair_type;
+ BitField<23, 1, u32> mole_type;
+ BitField<16, 7, u32> height;
+ BitField<15, 1, u32> hair_flip;
+ BitField<8, 7, u32> weight;
+ BitField<0, 7, u32> hair_color;
+ };
+
+ union {
+ u32 word_1;
+
+ BitField<31, 1, u32> gender;
+ BitField<24, 7, u32> eye_color;
+ BitField<16, 7, u32> eyebrow_color;
+ BitField<8, 7, u32> mouth_color;
+ BitField<0, 7, u32> facial_hair_color;
+ };
+
+ union {
+ u32 word_2;
+
+ BitField<31, 1, u32> mii_type;
+ BitField<24, 7, u32> glasses_color;
+ BitField<22, 2, u32> font_region;
+ BitField<16, 6, u32> eye_type;
+ BitField<14, 2, u32> mii_region;
+ BitField<8, 6, u32> mouth_type;
+ BitField<5, 3, u32> glasses_scale;
+ BitField<0, 5, u32> eye_y;
+ };
+
+ union {
+ u32 word_3;
+
+ BitField<29, 3, u32> mustache_type;
+ BitField<24, 5, u32> eyebrow_type;
+ BitField<21, 3, u32> beard_type;
+ BitField<16, 5, u32> nose_type;
+ BitField<13, 3, u32> mouth_aspect;
+ BitField<8, 5, u32> nose_y;
+ BitField<5, 3, u32> eyebrow_aspect;
+ BitField<0, 5, u32> mouth_y;
+ };
+
+ union {
+ u32 word_4;
+
+ BitField<29, 3, u32> eye_rotate;
+ BitField<24, 5, u32> mustache_y;
+ BitField<21, 3, u32> eye_aspect;
+ BitField<16, 5, u32> glasses_y;
+ BitField<13, 3, u32> eye_scale;
+ BitField<8, 5, u32> mole_x;
+ BitField<0, 5, u32> mole_y;
+ };
+
+ union {
+ u32 word_5;
+
+ BitField<24, 5, u32> glasses_type;
+ BitField<20, 4, u32> face_type;
+ BitField<16, 4, u32> favorite_color;
+ BitField<12, 4, u32> face_wrinkle;
+ BitField<8, 4, u32> face_color;
+ BitField<4, 4, u32> eye_x;
+ BitField<0, 4, u32> face_makeup;
+ };
+
+ union {
+ u32 word_6;
+
+ BitField<28, 4, u32> eyebrow_rotate;
+ BitField<24, 4, u32> eyebrow_scale;
+ BitField<20, 4, u32> eyebrow_y;
+ BitField<16, 4, u32> eyebrow_x;
+ BitField<12, 4, u32> mouth_scale;
+ BitField<8, 4, u32> nose_scale;
+ BitField<4, 4, u32> mole_scale;
+ BitField<0, 4, u32> mustache_scale;
+ };
+};
+static_assert(sizeof(MiiStoreBitFields) == 0x1C, "MiiStoreBitFields has incorrect size.");
+static_assert(std::is_trivially_copyable_v<MiiStoreBitFields>,
+ "MiiStoreBitFields is not trivially copyable.");
+
+struct MiiStoreData {
+ // This corresponds to the above structure MiiStoreBitFields. I did it like this because the
+ // BitField<> type makes this (and any thing that contains it) not trivially copyable, which is
+ // not suitable for our uses.
+ std::array<u8, 0x1C> data;
+ static_assert(sizeof(MiiStoreBitFields) == sizeof(data), "data field has incorrect size.");
+
+ std::array<char16_t, 10> name;
+ Common::UUID uuid;
+ u16 crc_1;
+ u16 crc_2;
+
+ std::u16string Name() const;
+};
+static_assert(sizeof(MiiStoreData) == 0x44, "MiiStoreData has incorrect size.");
+
+struct MiiStoreDataElement {
+ MiiStoreData data;
+ Source source;
+};
+static_assert(sizeof(MiiStoreDataElement) == 0x48, "MiiStoreDataElement has incorrect size.");
+
+struct MiiDatabase {
+ u32 magic; // 'NFDB'
+ std::array<MiiStoreData, MAX_MIIS> miis;
+ INSERT_PADDING_BYTES(1);
+ u8 count;
+ u16 crc;
+};
+static_assert(sizeof(MiiDatabase) == 0x1A98, "MiiDatabase has incorrect size.");
+#pragma pack(pop)
+
+// The Mii manager is responsible for loading and storing the Miis to the database in NAND along
+// with providing an easy interface for HLE emulation of the mii service.
+class MiiManager {
+public:
+ MiiManager();
+ ~MiiManager();
+
+ MiiInfo CreateRandom(RandomParameters params);
+ MiiInfo CreateDefault(u32 index);
+
+ bool CheckUpdatedFlag() const;
+ void ResetUpdatedFlag();
+
+ bool IsTestModeEnabled() const;
+
+ bool Empty() const;
+ bool Full() const;
+
+ void Clear();
+
+ u32 Size() const;
+
+ MiiInfo GetInfo(u32 index) const;
+ MiiInfoElement GetInfoElement(u32 index) const;
+ MiiStoreData GetStoreData(u32 index) const;
+ MiiStoreDataElement GetStoreDataElement(u32 index) const;
+
+ bool Remove(Common::UUID uuid);
+ u32 IndexOf(Common::UUID uuid) const;
+ u32 IndexOf(const MiiInfo& info) const;
+
+ bool Move(Common::UUID uuid, u32 new_index);
+ bool AddOrReplace(const MiiStoreData& data);
+
+ bool DestroyFile();
+ bool DeleteFile();
+
+private:
+ void WriteToFile();
+ void ReadFromFile();
+
+ MiiStoreData CreateMiiWithUniqueUUID() const;
+
+ void EnsureDatabasePartition();
+
+ MiiDatabase database;
+ bool updated_flag = false;
+ bool is_test_mode_enabled = false;
+};
+
+}; // namespace Service::Mii
diff --git a/src/core/hle/service/ncm/ncm.cpp b/src/core/hle/service/ncm/ncm.cpp
index 5d31f638f..b405a4b66 100644
--- a/src/core/hle/service/ncm/ncm.cpp
+++ b/src/core/hle/service/ncm/ncm.cpp
@@ -4,15 +4,89 @@
#include <memory>
+#include "core/file_sys/romfs_factory.h"
+#include "core/hle/ipc_helpers.h"
#include "core/hle/service/ncm/ncm.h"
#include "core/hle/service/service.h"
#include "core/hle/service/sm/sm.h"
namespace Service::NCM {
-class LocationResolver final : public ServiceFramework<LocationResolver> {
+class ILocationResolver final : public ServiceFramework<ILocationResolver> {
public:
- explicit LocationResolver() : ServiceFramework{"lr"} {
+ explicit ILocationResolver(FileSys::StorageId id)
+ : ServiceFramework{"ILocationResolver"}, storage(id) {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "ResolveProgramPath"},
+ {1, nullptr, "RedirectProgramPath"},
+ {2, nullptr, "ResolveApplicationControlPath"},
+ {3, nullptr, "ResolveApplicationHtmlDocumentPath"},
+ {4, nullptr, "ResolveDataPath"},
+ {5, nullptr, "RedirectApplicationControlPath"},
+ {6, nullptr, "RedirectApplicationHtmlDocumentPath"},
+ {7, nullptr, "ResolveApplicationLegalInformationPath"},
+ {8, nullptr, "RedirectApplicationLegalInformationPath"},
+ {9, nullptr, "Refresh"},
+ {10, nullptr, "RedirectProgramPath2"},
+ {11, nullptr, "Refresh2"},
+ {12, nullptr, "DeleteProgramPath"},
+ {13, nullptr, "DeleteApplicationControlPath"},
+ {14, nullptr, "DeleteApplicationHtmlDocumentPath"},
+ {15, nullptr, "DeleteApplicationLegalInformationPath"},
+ {16, nullptr, ""},
+ {17, nullptr, ""},
+ {18, nullptr, ""},
+ {19, nullptr, ""},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+
+private:
+ FileSys::StorageId storage;
+};
+
+class IRegisteredLocationResolver final : public ServiceFramework<IRegisteredLocationResolver> {
+public:
+ explicit IRegisteredLocationResolver() : ServiceFramework{"IRegisteredLocationResolver"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "ResolveProgramPath"},
+ {1, nullptr, "RegisterProgramPath"},
+ {2, nullptr, "UnregisterProgramPath"},
+ {3, nullptr, "RedirectProgramPath"},
+ {4, nullptr, "ResolveHtmlDocumentPath"},
+ {5, nullptr, "RegisterHtmlDocumentPath"},
+ {6, nullptr, "UnregisterHtmlDocumentPath"},
+ {7, nullptr, "RedirectHtmlDocumentPath"},
+ {8, nullptr, ""},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+};
+
+class IAddOnContentLocationResolver final : public ServiceFramework<IAddOnContentLocationResolver> {
+public:
+ explicit IAddOnContentLocationResolver() : ServiceFramework{"IAddOnContentLocationResolver"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "ResolveAddOnContentPath"},
+ {1, nullptr, "RegisterAddOnContentStorage"},
+ {2, nullptr, "UnregisterAllAddOnContentPath"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+};
+
+class LR final : public ServiceFramework<LR> {
+public:
+ explicit LR() : ServiceFramework{"lr"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "OpenLocationResolver"},
@@ -52,7 +126,7 @@ public:
};
void InstallInterfaces(SM::ServiceManager& sm) {
- std::make_shared<LocationResolver>()->InstallAsService(sm);
+ std::make_shared<LR>()->InstallAsService(sm);
std::make_shared<NCM>()->InstallAsService(sm);
}
diff --git a/src/core/hle/service/nfc/nfc.cpp b/src/core/hle/service/nfc/nfc.cpp
index 5c62d42ba..ca88bf97f 100644
--- a/src/core/hle/service/nfc/nfc.cpp
+++ b/src/core/hle/service/nfc/nfc.cpp
@@ -150,7 +150,7 @@ private:
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- rb.PushRaw<u8>(Settings::values.enable_nfc);
+ rb.PushRaw<u8>(true);
}
void GetStateOld(Kernel::HLERequestContext& ctx) {
diff --git a/src/core/hle/service/nfp/nfp.cpp b/src/core/hle/service/nfp/nfp.cpp
index 1c4482e47..a5cb06f8a 100644
--- a/src/core/hle/service/nfp/nfp.cpp
+++ b/src/core/hle/service/nfp/nfp.cpp
@@ -26,7 +26,7 @@ constexpr ResultCode ERR_NO_APPLICATION_AREA(ErrorModule::NFP, 152);
Module::Interface::Interface(std::shared_ptr<Module> module, const char* name)
: ServiceFramework(name), module(std::move(module)) {
auto& kernel = Core::System::GetInstance().Kernel();
- nfc_tag_load = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::OneShot,
+ nfc_tag_load = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Automatic,
"IUser:NFCTagDetected");
}
@@ -67,9 +67,9 @@ public:
auto& kernel = Core::System::GetInstance().Kernel();
deactivate_event = Kernel::WritableEvent::CreateEventPair(
- kernel, Kernel::ResetType::OneShot, "IUser:DeactivateEvent");
+ kernel, Kernel::ResetType::Automatic, "IUser:DeactivateEvent");
availability_change_event = Kernel::WritableEvent::CreateEventPair(
- kernel, Kernel::ResetType::OneShot, "IUser:AvailabilityChangeEvent");
+ kernel, Kernel::ResetType::Automatic, "IUser:AvailabilityChangeEvent");
}
private:
@@ -335,7 +335,7 @@ void Module::Interface::CreateUserInterface(Kernel::HLERequestContext& ctx) {
}
bool Module::Interface::LoadAmiibo(const std::vector<u8>& buffer) {
- std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
+ std::lock_guard lock{HLE::g_hle_lock};
if (buffer.size() < sizeof(AmiiboFile)) {
return false;
}
diff --git a/src/core/hle/service/nifm/nifm.cpp b/src/core/hle/service/nifm/nifm.cpp
index 60479bb45..76b12b482 100644
--- a/src/core/hle/service/nifm/nifm.cpp
+++ b/src/core/hle/service/nifm/nifm.cpp
@@ -15,12 +15,16 @@ namespace Service::NIFM {
class IScanRequest final : public ServiceFramework<IScanRequest> {
public:
explicit IScanRequest() : ServiceFramework("IScanRequest") {
+ // clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "Submit"},
{1, nullptr, "IsProcessing"},
{2, nullptr, "GetResult"},
{3, nullptr, "GetSystemEventReadableHandle"},
+ {4, nullptr, "SetChannels"},
};
+ // clang-format on
+
RegisterHandlers(functions);
}
};
@@ -58,9 +62,9 @@ public:
RegisterHandlers(functions);
auto& kernel = Core::System::GetInstance().Kernel();
- event1 = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::OneShot,
+ event1 = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Automatic,
"IRequest:Event1");
- event2 = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::OneShot,
+ event2 = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Automatic,
"IRequest:Event2");
}
diff --git a/src/core/hle/service/nim/nim.cpp b/src/core/hle/service/nim/nim.cpp
index 0dabcd23b..f319a3ca1 100644
--- a/src/core/hle/service/nim/nim.cpp
+++ b/src/core/hle/service/nim/nim.cpp
@@ -141,7 +141,7 @@ public:
auto& kernel = Core::System::GetInstance().Kernel();
finished_event = Kernel::WritableEvent::CreateEventPair(
- kernel, Kernel::ResetType::OneShot,
+ kernel, Kernel::ResetType::Automatic,
"IEnsureNetworkClockAvailabilityService:FinishEvent");
}
diff --git a/src/core/hle/service/npns/npns.cpp b/src/core/hle/service/npns/npns.cpp
index ccb6f9da9..8751522ca 100644
--- a/src/core/hle/service/npns/npns.cpp
+++ b/src/core/hle/service/npns/npns.cpp
@@ -45,7 +45,7 @@ public:
{114, nullptr, "AttachJid"},
{115, nullptr, "DetachJid"},
{201, nullptr, "RequestChangeStateForceTimed"},
- {102, nullptr, "RequestChangeStateForceAsync"},
+ {202, nullptr, "RequestChangeStateForceAsync"},
};
// clang-format on
@@ -73,6 +73,7 @@ public:
{103, nullptr, "GetState"},
{104, nullptr, "GetStatistics"},
{111, nullptr, "GetJid"},
+ {120, nullptr, "CreateNotificationReceiver"},
};
// clang-format on
diff --git a/src/core/hle/service/ns/errors.h b/src/core/hle/service/ns/errors.h
new file mode 100644
index 000000000..f4aea8a65
--- /dev/null
+++ b/src/core/hle/service/ns/errors.h
@@ -0,0 +1,12 @@
+// Copyright 2019 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::NS {
+
+constexpr ResultCode ERR_APPLICATION_LANGUAGE_NOT_FOUND{ErrorModule::NS, 300};
+} \ No newline at end of file
diff --git a/src/core/hle/service/ns/language.cpp b/src/core/hle/service/ns/language.cpp
new file mode 100644
index 000000000..29c4a820c
--- /dev/null
+++ b/src/core/hle/service/ns/language.cpp
@@ -0,0 +1,392 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/ns/language.h"
+#include "core/hle/service/set/set.h"
+
+namespace Service::NS {
+
+constexpr ApplicationLanguagePriorityList priority_list_american_english = {{
+ ApplicationLanguage::AmericanEnglish,
+ ApplicationLanguage::BritishEnglish,
+ ApplicationLanguage::LatinAmericanSpanish,
+ ApplicationLanguage::CanadianFrench,
+ ApplicationLanguage::French,
+ ApplicationLanguage::German,
+ ApplicationLanguage::Spanish,
+ ApplicationLanguage::Italian,
+ ApplicationLanguage::Dutch,
+ ApplicationLanguage::Portuguese,
+ ApplicationLanguage::Russian,
+ ApplicationLanguage::Japanese,
+ ApplicationLanguage::SimplifiedChinese,
+ ApplicationLanguage::TraditionalChinese,
+ ApplicationLanguage::Korean,
+}};
+
+constexpr ApplicationLanguagePriorityList priority_list_british_english = {{
+ ApplicationLanguage::BritishEnglish,
+ ApplicationLanguage::AmericanEnglish,
+ ApplicationLanguage::French,
+ ApplicationLanguage::German,
+ ApplicationLanguage::Spanish,
+ ApplicationLanguage::Italian,
+ ApplicationLanguage::Dutch,
+ ApplicationLanguage::Portuguese,
+ ApplicationLanguage::Russian,
+ ApplicationLanguage::LatinAmericanSpanish,
+ ApplicationLanguage::CanadianFrench,
+ ApplicationLanguage::Japanese,
+ ApplicationLanguage::SimplifiedChinese,
+ ApplicationLanguage::TraditionalChinese,
+ ApplicationLanguage::Korean,
+}};
+
+constexpr ApplicationLanguagePriorityList priority_list_japanese = {{
+ ApplicationLanguage::Japanese,
+ ApplicationLanguage::AmericanEnglish,
+ ApplicationLanguage::BritishEnglish,
+ ApplicationLanguage::LatinAmericanSpanish,
+ ApplicationLanguage::CanadianFrench,
+ ApplicationLanguage::French,
+ ApplicationLanguage::German,
+ ApplicationLanguage::Spanish,
+ ApplicationLanguage::Italian,
+ ApplicationLanguage::Dutch,
+ ApplicationLanguage::Portuguese,
+ ApplicationLanguage::Russian,
+ ApplicationLanguage::SimplifiedChinese,
+ ApplicationLanguage::TraditionalChinese,
+ ApplicationLanguage::Korean,
+}};
+
+constexpr ApplicationLanguagePriorityList priority_list_french = {{
+ ApplicationLanguage::French,
+ ApplicationLanguage::CanadianFrench,
+ ApplicationLanguage::BritishEnglish,
+ ApplicationLanguage::AmericanEnglish,
+ ApplicationLanguage::German,
+ ApplicationLanguage::Spanish,
+ ApplicationLanguage::Italian,
+ ApplicationLanguage::Dutch,
+ ApplicationLanguage::Portuguese,
+ ApplicationLanguage::Russian,
+ ApplicationLanguage::LatinAmericanSpanish,
+ ApplicationLanguage::Japanese,
+ ApplicationLanguage::SimplifiedChinese,
+ ApplicationLanguage::TraditionalChinese,
+ ApplicationLanguage::Korean,
+}};
+
+constexpr ApplicationLanguagePriorityList priority_list_german = {{
+ ApplicationLanguage::German,
+ ApplicationLanguage::BritishEnglish,
+ ApplicationLanguage::AmericanEnglish,
+ ApplicationLanguage::French,
+ ApplicationLanguage::Spanish,
+ ApplicationLanguage::Italian,
+ ApplicationLanguage::Dutch,
+ ApplicationLanguage::Portuguese,
+ ApplicationLanguage::Russian,
+ ApplicationLanguage::LatinAmericanSpanish,
+ ApplicationLanguage::CanadianFrench,
+ ApplicationLanguage::Japanese,
+ ApplicationLanguage::SimplifiedChinese,
+ ApplicationLanguage::TraditionalChinese,
+ ApplicationLanguage::Korean,
+}};
+
+constexpr ApplicationLanguagePriorityList priority_list_latin_american_spanish = {{
+ ApplicationLanguage::LatinAmericanSpanish,
+ ApplicationLanguage::Spanish,
+ ApplicationLanguage::AmericanEnglish,
+ ApplicationLanguage::BritishEnglish,
+ ApplicationLanguage::Portuguese,
+ ApplicationLanguage::CanadianFrench,
+ ApplicationLanguage::French,
+ ApplicationLanguage::Italian,
+ ApplicationLanguage::German,
+ ApplicationLanguage::Dutch,
+ ApplicationLanguage::Russian,
+ ApplicationLanguage::Japanese,
+ ApplicationLanguage::SimplifiedChinese,
+ ApplicationLanguage::TraditionalChinese,
+ ApplicationLanguage::Korean,
+}};
+
+constexpr ApplicationLanguagePriorityList priority_list_spanish = {{
+ ApplicationLanguage::Spanish,
+ ApplicationLanguage::LatinAmericanSpanish,
+ ApplicationLanguage::BritishEnglish,
+ ApplicationLanguage::AmericanEnglish,
+ ApplicationLanguage::French,
+ ApplicationLanguage::German,
+ ApplicationLanguage::Italian,
+ ApplicationLanguage::Dutch,
+ ApplicationLanguage::Portuguese,
+ ApplicationLanguage::Russian,
+ ApplicationLanguage::CanadianFrench,
+ ApplicationLanguage::Japanese,
+ ApplicationLanguage::SimplifiedChinese,
+ ApplicationLanguage::TraditionalChinese,
+ ApplicationLanguage::Korean,
+}};
+
+constexpr ApplicationLanguagePriorityList priority_list_italian = {{
+ ApplicationLanguage::Italian,
+ ApplicationLanguage::BritishEnglish,
+ ApplicationLanguage::AmericanEnglish,
+ ApplicationLanguage::French,
+ ApplicationLanguage::German,
+ ApplicationLanguage::Spanish,
+ ApplicationLanguage::Dutch,
+ ApplicationLanguage::Portuguese,
+ ApplicationLanguage::Russian,
+ ApplicationLanguage::LatinAmericanSpanish,
+ ApplicationLanguage::CanadianFrench,
+ ApplicationLanguage::Japanese,
+ ApplicationLanguage::SimplifiedChinese,
+ ApplicationLanguage::TraditionalChinese,
+ ApplicationLanguage::Korean,
+}};
+
+constexpr ApplicationLanguagePriorityList priority_list_dutch = {{
+ ApplicationLanguage::Dutch,
+ ApplicationLanguage::BritishEnglish,
+ ApplicationLanguage::AmericanEnglish,
+ ApplicationLanguage::French,
+ ApplicationLanguage::German,
+ ApplicationLanguage::Spanish,
+ ApplicationLanguage::Italian,
+ ApplicationLanguage::Portuguese,
+ ApplicationLanguage::Russian,
+ ApplicationLanguage::LatinAmericanSpanish,
+ ApplicationLanguage::CanadianFrench,
+ ApplicationLanguage::Japanese,
+ ApplicationLanguage::SimplifiedChinese,
+ ApplicationLanguage::TraditionalChinese,
+ ApplicationLanguage::Korean,
+}};
+
+constexpr ApplicationLanguagePriorityList priority_list_canadian_french = {{
+ ApplicationLanguage::CanadianFrench,
+ ApplicationLanguage::French,
+ ApplicationLanguage::AmericanEnglish,
+ ApplicationLanguage::BritishEnglish,
+ ApplicationLanguage::LatinAmericanSpanish,
+ ApplicationLanguage::Spanish,
+ ApplicationLanguage::German,
+ ApplicationLanguage::Italian,
+ ApplicationLanguage::Dutch,
+ ApplicationLanguage::Portuguese,
+ ApplicationLanguage::Russian,
+ ApplicationLanguage::Japanese,
+ ApplicationLanguage::SimplifiedChinese,
+ ApplicationLanguage::TraditionalChinese,
+ ApplicationLanguage::Korean,
+}};
+
+constexpr ApplicationLanguagePriorityList priority_list_portuguese = {{
+ ApplicationLanguage::Portuguese,
+ ApplicationLanguage::BritishEnglish,
+ ApplicationLanguage::AmericanEnglish,
+ ApplicationLanguage::French,
+ ApplicationLanguage::German,
+ ApplicationLanguage::Spanish,
+ ApplicationLanguage::Italian,
+ ApplicationLanguage::Dutch,
+ ApplicationLanguage::Russian,
+ ApplicationLanguage::LatinAmericanSpanish,
+ ApplicationLanguage::CanadianFrench,
+ ApplicationLanguage::Japanese,
+ ApplicationLanguage::SimplifiedChinese,
+ ApplicationLanguage::TraditionalChinese,
+ ApplicationLanguage::Korean,
+}};
+
+constexpr ApplicationLanguagePriorityList priority_list_russian = {{
+ ApplicationLanguage::Russian,
+ ApplicationLanguage::BritishEnglish,
+ ApplicationLanguage::AmericanEnglish,
+ ApplicationLanguage::French,
+ ApplicationLanguage::German,
+ ApplicationLanguage::Spanish,
+ ApplicationLanguage::Italian,
+ ApplicationLanguage::Dutch,
+ ApplicationLanguage::Portuguese,
+ ApplicationLanguage::LatinAmericanSpanish,
+ ApplicationLanguage::CanadianFrench,
+ ApplicationLanguage::Japanese,
+ ApplicationLanguage::SimplifiedChinese,
+ ApplicationLanguage::TraditionalChinese,
+ ApplicationLanguage::Korean,
+}};
+
+constexpr ApplicationLanguagePriorityList priority_list_korean = {{
+ ApplicationLanguage::Korean,
+ ApplicationLanguage::AmericanEnglish,
+ ApplicationLanguage::BritishEnglish,
+ ApplicationLanguage::LatinAmericanSpanish,
+ ApplicationLanguage::CanadianFrench,
+ ApplicationLanguage::French,
+ ApplicationLanguage::German,
+ ApplicationLanguage::Spanish,
+ ApplicationLanguage::Italian,
+ ApplicationLanguage::Dutch,
+ ApplicationLanguage::Portuguese,
+ ApplicationLanguage::Russian,
+ ApplicationLanguage::Japanese,
+ ApplicationLanguage::SimplifiedChinese,
+ ApplicationLanguage::TraditionalChinese,
+}};
+
+constexpr ApplicationLanguagePriorityList priority_list_traditional_chinese = {{
+ ApplicationLanguage::TraditionalChinese,
+ ApplicationLanguage::SimplifiedChinese,
+ ApplicationLanguage::AmericanEnglish,
+ ApplicationLanguage::BritishEnglish,
+ ApplicationLanguage::Japanese,
+ ApplicationLanguage::LatinAmericanSpanish,
+ ApplicationLanguage::CanadianFrench,
+ ApplicationLanguage::French,
+ ApplicationLanguage::German,
+ ApplicationLanguage::Spanish,
+ ApplicationLanguage::Italian,
+ ApplicationLanguage::Dutch,
+ ApplicationLanguage::Portuguese,
+ ApplicationLanguage::Russian,
+ ApplicationLanguage::Korean,
+}};
+
+constexpr ApplicationLanguagePriorityList priority_list_simplified_chinese = {{
+ ApplicationLanguage::SimplifiedChinese,
+ ApplicationLanguage::TraditionalChinese,
+ ApplicationLanguage::AmericanEnglish,
+ ApplicationLanguage::BritishEnglish,
+ ApplicationLanguage::Japanese,
+ ApplicationLanguage::LatinAmericanSpanish,
+ ApplicationLanguage::CanadianFrench,
+ ApplicationLanguage::French,
+ ApplicationLanguage::German,
+ ApplicationLanguage::Spanish,
+ ApplicationLanguage::Italian,
+ ApplicationLanguage::Dutch,
+ ApplicationLanguage::Portuguese,
+ ApplicationLanguage::Russian,
+ ApplicationLanguage::Korean,
+}};
+
+const ApplicationLanguagePriorityList* GetApplicationLanguagePriorityList(
+ const ApplicationLanguage lang) {
+ switch (lang) {
+ case ApplicationLanguage::AmericanEnglish:
+ return &priority_list_american_english;
+ case ApplicationLanguage::BritishEnglish:
+ return &priority_list_british_english;
+ case ApplicationLanguage::Japanese:
+ return &priority_list_japanese;
+ case ApplicationLanguage::French:
+ return &priority_list_french;
+ case ApplicationLanguage::German:
+ return &priority_list_german;
+ case ApplicationLanguage::LatinAmericanSpanish:
+ return &priority_list_latin_american_spanish;
+ case ApplicationLanguage::Spanish:
+ return &priority_list_spanish;
+ case ApplicationLanguage::Italian:
+ return &priority_list_italian;
+ case ApplicationLanguage::Dutch:
+ return &priority_list_dutch;
+ case ApplicationLanguage::CanadianFrench:
+ return &priority_list_canadian_french;
+ case ApplicationLanguage::Portuguese:
+ return &priority_list_portuguese;
+ case ApplicationLanguage::Russian:
+ return &priority_list_russian;
+ case ApplicationLanguage::Korean:
+ return &priority_list_korean;
+ case ApplicationLanguage::TraditionalChinese:
+ return &priority_list_traditional_chinese;
+ case ApplicationLanguage::SimplifiedChinese:
+ return &priority_list_simplified_chinese;
+ default:
+ return nullptr;
+ }
+}
+
+std::optional<ApplicationLanguage> ConvertToApplicationLanguage(
+ const Set::LanguageCode language_code) {
+ switch (language_code) {
+ case Set::LanguageCode::EN_US:
+ return ApplicationLanguage::AmericanEnglish;
+ case Set::LanguageCode::EN_GB:
+ return ApplicationLanguage::BritishEnglish;
+ case Set::LanguageCode::JA:
+ return ApplicationLanguage::Japanese;
+ case Set::LanguageCode::FR:
+ return ApplicationLanguage::French;
+ case Set::LanguageCode::DE:
+ return ApplicationLanguage::German;
+ case Set::LanguageCode::ES_419:
+ return ApplicationLanguage::LatinAmericanSpanish;
+ case Set::LanguageCode::ES:
+ return ApplicationLanguage::Spanish;
+ case Set::LanguageCode::IT:
+ return ApplicationLanguage::Italian;
+ case Set::LanguageCode::NL:
+ return ApplicationLanguage::Dutch;
+ case Set::LanguageCode::FR_CA:
+ return ApplicationLanguage::CanadianFrench;
+ case Set::LanguageCode::PT:
+ return ApplicationLanguage::Portuguese;
+ case Set::LanguageCode::RU:
+ return ApplicationLanguage::Russian;
+ case Set::LanguageCode::KO:
+ return ApplicationLanguage::Korean;
+ case Set::LanguageCode::ZH_HANT:
+ return ApplicationLanguage::TraditionalChinese;
+ case Set::LanguageCode::ZH_HANS:
+ return ApplicationLanguage::SimplifiedChinese;
+ default:
+ return std::nullopt;
+ }
+}
+
+std::optional<Set::LanguageCode> ConvertToLanguageCode(const ApplicationLanguage lang) {
+ switch (lang) {
+ case ApplicationLanguage::AmericanEnglish:
+ return Set::LanguageCode::EN_US;
+ case ApplicationLanguage::BritishEnglish:
+ return Set::LanguageCode::EN_GB;
+ case ApplicationLanguage::Japanese:
+ return Set::LanguageCode::JA;
+ case ApplicationLanguage::French:
+ return Set::LanguageCode::FR;
+ case ApplicationLanguage::German:
+ return Set::LanguageCode::DE;
+ case ApplicationLanguage::LatinAmericanSpanish:
+ return Set::LanguageCode::ES_419;
+ case ApplicationLanguage::Spanish:
+ return Set::LanguageCode::ES;
+ case ApplicationLanguage::Italian:
+ return Set::LanguageCode::IT;
+ case ApplicationLanguage::Dutch:
+ return Set::LanguageCode::NL;
+ case ApplicationLanguage::CanadianFrench:
+ return Set::LanguageCode::FR_CA;
+ case ApplicationLanguage::Portuguese:
+ return Set::LanguageCode::PT;
+ case ApplicationLanguage::Russian:
+ return Set::LanguageCode::RU;
+ case ApplicationLanguage::Korean:
+ return Set::LanguageCode::KO;
+ case ApplicationLanguage::TraditionalChinese:
+ return Set::LanguageCode::ZH_HANT;
+ case ApplicationLanguage::SimplifiedChinese:
+ return Set::LanguageCode::ZH_HANS;
+ default:
+ return std::nullopt;
+ }
+}
+} // namespace Service::NS \ No newline at end of file
diff --git a/src/core/hle/service/ns/language.h b/src/core/hle/service/ns/language.h
new file mode 100644
index 000000000..e9829f9d2
--- /dev/null
+++ b/src/core/hle/service/ns/language.h
@@ -0,0 +1,45 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <optional>
+#include <string>
+#include "common/common_types.h"
+
+namespace Service::Set {
+enum class LanguageCode : u64;
+}
+
+namespace Service::NS {
+/// This is nn::ns::detail::ApplicationLanguage
+enum class ApplicationLanguage : u8 {
+ AmericanEnglish = 0,
+ BritishEnglish,
+ Japanese,
+ French,
+ German,
+ LatinAmericanSpanish,
+ Spanish,
+ Italian,
+ Dutch,
+ CanadianFrench,
+ Portuguese,
+ Russian,
+ Korean,
+ TraditionalChinese,
+ SimplifiedChinese,
+ Count
+};
+using ApplicationLanguagePriorityList =
+ const std::array<ApplicationLanguage, static_cast<std::size_t>(ApplicationLanguage::Count)>;
+
+constexpr u32 GetSupportedLanguageFlag(const ApplicationLanguage lang) {
+ return 1U << static_cast<u32>(lang);
+}
+
+const ApplicationLanguagePriorityList* GetApplicationLanguagePriorityList(ApplicationLanguage lang);
+std::optional<ApplicationLanguage> ConvertToApplicationLanguage(Set::LanguageCode language_code);
+std::optional<Set::LanguageCode> ConvertToLanguageCode(ApplicationLanguage lang);
+} // namespace Service::NS \ No newline at end of file
diff --git a/src/core/hle/service/ns/ns.cpp b/src/core/hle/service/ns/ns.cpp
index 0eb04037a..ce88a2941 100644
--- a/src/core/hle/service/ns/ns.cpp
+++ b/src/core/hle/service/ns/ns.cpp
@@ -7,445 +7,507 @@
#include "core/file_sys/patch_manager.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/hle_ipc.h"
+#include "core/hle/service/ns/errors.h"
+#include "core/hle/service/ns/language.h"
#include "core/hle/service/ns/ns.h"
#include "core/hle/service/ns/pl_u.h"
+#include "core/hle/service/set/set.h"
+#include "core/settings.h"
namespace Service::NS {
-class IAccountProxyInterface final : public ServiceFramework<IAccountProxyInterface> {
-public:
- explicit IAccountProxyInterface() : ServiceFramework{"IAccountProxyInterface"} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {0, nullptr, "CreateUserAccount"},
- };
- // clang-format on
+IAccountProxyInterface::IAccountProxyInterface() : ServiceFramework{"IAccountProxyInterface"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "CreateUserAccount"},
+ };
+ // clang-format on
- RegisterHandlers(functions);
- }
-};
+ RegisterHandlers(functions);
+}
-class IApplicationManagerInterface final : public ServiceFramework<IApplicationManagerInterface> {
-public:
- explicit IApplicationManagerInterface() : ServiceFramework{"IApplicationManagerInterface"} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {0, nullptr, "ListApplicationRecord"},
- {1, nullptr, "GenerateApplicationRecordCount"},
- {2, nullptr, "GetApplicationRecordUpdateSystemEvent"},
- {3, nullptr, "GetApplicationViewDeprecated"},
- {4, nullptr, "DeleteApplicationEntity"},
- {5, nullptr, "DeleteApplicationCompletely"},
- {6, nullptr, "IsAnyApplicationEntityRedundant"},
- {7, nullptr, "DeleteRedundantApplicationEntity"},
- {8, nullptr, "IsApplicationEntityMovable"},
- {9, nullptr, "MoveApplicationEntity"},
- {11, nullptr, "CalculateApplicationOccupiedSize"},
- {16, nullptr, "PushApplicationRecord"},
- {17, nullptr, "ListApplicationRecordContentMeta"},
- {19, nullptr, "LaunchApplicationOld"},
- {21, nullptr, "GetApplicationContentPath"},
- {22, nullptr, "TerminateApplication"},
- {23, nullptr, "ResolveApplicationContentPath"},
- {26, nullptr, "BeginInstallApplication"},
- {27, nullptr, "DeleteApplicationRecord"},
- {30, nullptr, "RequestApplicationUpdateInfo"},
- {32, nullptr, "CancelApplicationDownload"},
- {33, nullptr, "ResumeApplicationDownload"},
- {35, nullptr, "UpdateVersionList"},
- {36, nullptr, "PushLaunchVersion"},
- {37, nullptr, "ListRequiredVersion"},
- {38, nullptr, "CheckApplicationLaunchVersion"},
- {39, nullptr, "CheckApplicationLaunchRights"},
- {40, nullptr, "GetApplicationLogoData"},
- {41, nullptr, "CalculateApplicationDownloadRequiredSize"},
- {42, nullptr, "CleanupSdCard"},
- {43, nullptr, "CheckSdCardMountStatus"},
- {44, nullptr, "GetSdCardMountStatusChangedEvent"},
- {45, nullptr, "GetGameCardAttachmentEvent"},
- {46, nullptr, "GetGameCardAttachmentInfo"},
- {47, nullptr, "GetTotalSpaceSize"},
- {48, nullptr, "GetFreeSpaceSize"},
- {49, nullptr, "GetSdCardRemovedEvent"},
- {52, nullptr, "GetGameCardUpdateDetectionEvent"},
- {53, nullptr, "DisableApplicationAutoDelete"},
- {54, nullptr, "EnableApplicationAutoDelete"},
- {55, nullptr, "GetApplicationDesiredLanguage"},
- {56, nullptr, "SetApplicationTerminateResult"},
- {57, nullptr, "ClearApplicationTerminateResult"},
- {58, nullptr, "GetLastSdCardMountUnexpectedResult"},
- {59, nullptr, "ConvertApplicationLanguageToLanguageCode"},
- {60, nullptr, "ConvertLanguageCodeToApplicationLanguage"},
- {61, nullptr, "GetBackgroundDownloadStressTaskInfo"},
- {62, nullptr, "GetGameCardStopper"},
- {63, nullptr, "IsSystemProgramInstalled"},
- {64, nullptr, "StartApplyDeltaTask"},
- {65, nullptr, "GetRequestServerStopper"},
- {66, nullptr, "GetBackgroundApplyDeltaStressTaskInfo"},
- {67, nullptr, "CancelApplicationApplyDelta"},
- {68, nullptr, "ResumeApplicationApplyDelta"},
- {69, nullptr, "CalculateApplicationApplyDeltaRequiredSize"},
- {70, nullptr, "ResumeAll"},
- {71, nullptr, "GetStorageSize"},
- {80, nullptr, "RequestDownloadApplication"},
- {81, nullptr, "RequestDownloadAddOnContent"},
- {82, nullptr, "DownloadApplication"},
- {83, nullptr, "CheckApplicationResumeRights"},
- {84, nullptr, "GetDynamicCommitEvent"},
- {85, nullptr, "RequestUpdateApplication2"},
- {86, nullptr, "EnableApplicationCrashReport"},
- {87, nullptr, "IsApplicationCrashReportEnabled"},
- {90, nullptr, "BoostSystemMemoryResourceLimit"},
- {91, nullptr, "DeprecatedLaunchApplication"},
- {92, nullptr, "GetRunningApplicationProgramId"},
- {93, nullptr, "GetMainApplicationProgramIndex"},
- {94, nullptr, "LaunchApplication"},
- {95, nullptr, "GetApplicationLaunchInfo"},
- {96, nullptr, "AcquireApplicationLaunchInfo"},
- {97, nullptr, "GetMainApplicationProgramIndex2"},
- {98, nullptr, "EnableApplicationAllThreadDumpOnCrash"},
- {100, nullptr, "ResetToFactorySettings"},
- {101, nullptr, "ResetToFactorySettingsWithoutUserSaveData"},
- {102, nullptr, "ResetToFactorySettingsForRefurbishment"},
- {200, nullptr, "CalculateUserSaveDataStatistics"},
- {201, nullptr, "DeleteUserSaveDataAll"},
- {210, nullptr, "DeleteUserSystemSaveData"},
- {211, nullptr, "DeleteSaveData"},
- {220, nullptr, "UnregisterNetworkServiceAccount"},
- {221, nullptr, "UnregisterNetworkServiceAccountWithUserSaveDataDeletion"},
- {300, nullptr, "GetApplicationShellEvent"},
- {301, nullptr, "PopApplicationShellEventInfo"},
- {302, nullptr, "LaunchLibraryApplet"},
- {303, nullptr, "TerminateLibraryApplet"},
- {304, nullptr, "LaunchSystemApplet"},
- {305, nullptr, "TerminateSystemApplet"},
- {306, nullptr, "LaunchOverlayApplet"},
- {307, nullptr, "TerminateOverlayApplet"},
- {400, &IApplicationManagerInterface::GetApplicationControlData, "GetApplicationControlData"},
- {401, nullptr, "InvalidateAllApplicationControlCache"},
- {402, nullptr, "RequestDownloadApplicationControlData"},
- {403, nullptr, "GetMaxApplicationControlCacheCount"},
- {404, nullptr, "InvalidateApplicationControlCache"},
- {405, nullptr, "ListApplicationControlCacheEntryInfo"},
- {406, nullptr, "GetApplicationControlProperty"},
- {502, nullptr, "RequestCheckGameCardRegistration"},
- {503, nullptr, "RequestGameCardRegistrationGoldPoint"},
- {504, nullptr, "RequestRegisterGameCard"},
- {505, nullptr, "GetGameCardMountFailureEvent"},
- {506, nullptr, "IsGameCardInserted"},
- {507, nullptr, "EnsureGameCardAccess"},
- {508, nullptr, "GetLastGameCardMountFailureResult"},
- {509, nullptr, "ListApplicationIdOnGameCard"},
- {600, nullptr, "CountApplicationContentMeta"},
- {601, nullptr, "ListApplicationContentMetaStatus"},
- {602, nullptr, "ListAvailableAddOnContent"},
- {603, nullptr, "GetOwnedApplicationContentMetaStatus"},
- {604, nullptr, "RegisterContentsExternalKey"},
- {605, nullptr, "ListApplicationContentMetaStatusWithRightsCheck"},
- {606, nullptr, "GetContentMetaStorage"},
- {607, nullptr, "ListAvailableAddOnContent"},
- {700, nullptr, "PushDownloadTaskList"},
- {701, nullptr, "ClearTaskStatusList"},
- {702, nullptr, "RequestDownloadTaskList"},
- {703, nullptr, "RequestEnsureDownloadTask"},
- {704, nullptr, "ListDownloadTaskStatus"},
- {705, nullptr, "RequestDownloadTaskListData"},
- {800, nullptr, "RequestVersionList"},
- {801, nullptr, "ListVersionList"},
- {802, nullptr, "RequestVersionListData"},
- {900, nullptr, "GetApplicationRecord"},
- {901, nullptr, "GetApplicationRecordProperty"},
- {902, nullptr, "EnableApplicationAutoUpdate"},
- {903, nullptr, "DisableApplicationAutoUpdate"},
- {904, nullptr, "TouchApplication"},
- {905, nullptr, "RequestApplicationUpdate"},
- {906, nullptr, "IsApplicationUpdateRequested"},
- {907, nullptr, "WithdrawApplicationUpdateRequest"},
- {908, nullptr, "ListApplicationRecordInstalledContentMeta"},
- {909, nullptr, "WithdrawCleanupAddOnContentsWithNoRightsRecommendation"},
- {910, nullptr, "HasApplicationRecord"},
- {911, nullptr, "SetPreInstalledApplication"},
- {912, nullptr, "ClearPreInstalledApplicationFlag"},
- {1000, nullptr, "RequestVerifyApplicationDeprecated"},
- {1001, nullptr, "CorruptApplicationForDebug"},
- {1002, nullptr, "RequestVerifyAddOnContentsRights"},
- {1003, nullptr, "RequestVerifyApplication"},
- {1004, nullptr, "CorruptContentForDebug"},
- {1200, nullptr, "NeedsUpdateVulnerability"},
- {1300, nullptr, "IsAnyApplicationEntityInstalled"},
- {1301, nullptr, "DeleteApplicationContentEntities"},
- {1302, nullptr, "CleanupUnrecordedApplicationEntity"},
- {1303, nullptr, "CleanupAddOnContentsWithNoRights"},
- {1304, nullptr, "DeleteApplicationContentEntity"},
- {1305, nullptr, "TryDeleteRunningApplicationEntity"},
- {1306, nullptr, "TryDeleteRunningApplicationCompletely"},
- {1307, nullptr, "TryDeleteRunningApplicationContentEntities"},
- {1308, nullptr, "DeleteApplicationCompletelyForDebug"},
- {1309, nullptr, "CleanupUnavailableAddOnContents"},
- {1400, nullptr, "PrepareShutdown"},
- {1500, nullptr, "FormatSdCard"},
- {1501, nullptr, "NeedsSystemUpdateToFormatSdCard"},
- {1502, nullptr, "GetLastSdCardFormatUnexpectedResult"},
- {1504, nullptr, "InsertSdCard"},
- {1505, nullptr, "RemoveSdCard"},
- {1600, nullptr, "GetSystemSeedForPseudoDeviceId"},
- {1601, nullptr, "ResetSystemSeedForPseudoDeviceId"},
- {1700, nullptr, "ListApplicationDownloadingContentMeta"},
- {1701, nullptr, "GetApplicationView"},
- {1702, nullptr, "GetApplicationDownloadTaskStatus"},
- {1703, nullptr, "GetApplicationViewDownloadErrorContext"},
- {1800, nullptr, "IsNotificationSetupCompleted"},
- {1801, nullptr, "GetLastNotificationInfoCount"},
- {1802, nullptr, "ListLastNotificationInfo"},
- {1803, nullptr, "ListNotificationTask"},
- {1900, nullptr, "IsActiveAccount"},
- {1901, nullptr, "RequestDownloadApplicationPrepurchasedRights"},
- {1902, nullptr, "GetApplicationTicketInfo"},
- {2000, nullptr, "GetSystemDeliveryInfo"},
- {2001, nullptr, "SelectLatestSystemDeliveryInfo"},
- {2002, nullptr, "VerifyDeliveryProtocolVersion"},
- {2003, nullptr, "GetApplicationDeliveryInfo"},
- {2004, nullptr, "HasAllContentsToDeliver"},
- {2005, nullptr, "CompareApplicationDeliveryInfo"},
- {2006, nullptr, "CanDeliverApplication"},
- {2007, nullptr, "ListContentMetaKeyToDeliverApplication"},
- {2008, nullptr, "NeedsSystemUpdateToDeliverApplication"},
- {2009, nullptr, "EstimateRequiredSize"},
- {2010, nullptr, "RequestReceiveApplication"},
- {2011, nullptr, "CommitReceiveApplication"},
- {2012, nullptr, "GetReceiveApplicationProgress"},
- {2013, nullptr, "RequestSendApplication"},
- {2014, nullptr, "GetSendApplicationProgress"},
- {2015, nullptr, "CompareSystemDeliveryInfo"},
- {2016, nullptr, "ListNotCommittedContentMeta"},
- {2017, nullptr, "CreateDownloadTask"},
- {2018, nullptr, "GetApplicationDeliveryInfoHash"},
- {2050, nullptr, "GetApplicationRightsOnClient"},
- {2100, nullptr, "GetApplicationTerminateResult"},
- {2101, nullptr, "GetRawApplicationTerminateResult"},
- {2150, nullptr, "CreateRightsEnvironment"},
- {2151, nullptr, "DestroyRightsEnvironment"},
- {2152, nullptr, "ActivateRightsEnvironment"},
- {2153, nullptr, "DeactivateRightsEnvironment"},
- {2154, nullptr, "ForceActivateRightsContextForExit"},
- {2160, nullptr, "AddTargetApplicationToRightsEnvironment"},
- {2161, nullptr, "SetUsersToRightsEnvironment"},
- {2170, nullptr, "GetRightsEnvironmentStatus"},
- {2171, nullptr, "GetRightsEnvironmentStatusChangedEvent"},
- {2180, nullptr, "RequestExtendRightsInRightsEnvironment"},
- {2181, nullptr, "GetLastResultOfExtendRightsInRightsEnvironment"},
- {2182, nullptr, "SetActiveRightsContextUsingStateToRightsEnvironment"},
- {2190, nullptr, "GetRightsEnvironmentHandleForApplication"},
- {2199, nullptr, "GetRightsEnvironmentCountForDebug"},
- {2200, nullptr, "GetGameCardApplicationCopyIdentifier"},
- {2201, nullptr, "GetInstalledApplicationCopyIdentifier"},
- {2250, nullptr, "RequestReportActiveELicence"},
- {2300, nullptr, "ListEventLog"},
- };
- // clang-format on
+IAccountProxyInterface::~IAccountProxyInterface() = default;
+
+IApplicationManagerInterface::IApplicationManagerInterface()
+ : ServiceFramework{"IApplicationManagerInterface"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "ListApplicationRecord"},
+ {1, nullptr, "GenerateApplicationRecordCount"},
+ {2, nullptr, "GetApplicationRecordUpdateSystemEvent"},
+ {3, nullptr, "GetApplicationViewDeprecated"},
+ {4, nullptr, "DeleteApplicationEntity"},
+ {5, nullptr, "DeleteApplicationCompletely"},
+ {6, nullptr, "IsAnyApplicationEntityRedundant"},
+ {7, nullptr, "DeleteRedundantApplicationEntity"},
+ {8, nullptr, "IsApplicationEntityMovable"},
+ {9, nullptr, "MoveApplicationEntity"},
+ {11, nullptr, "CalculateApplicationOccupiedSize"},
+ {16, nullptr, "PushApplicationRecord"},
+ {17, nullptr, "ListApplicationRecordContentMeta"},
+ {19, nullptr, "LaunchApplicationOld"},
+ {21, nullptr, "GetApplicationContentPath"},
+ {22, nullptr, "TerminateApplication"},
+ {23, nullptr, "ResolveApplicationContentPath"},
+ {26, nullptr, "BeginInstallApplication"},
+ {27, nullptr, "DeleteApplicationRecord"},
+ {30, nullptr, "RequestApplicationUpdateInfo"},
+ {32, nullptr, "CancelApplicationDownload"},
+ {33, nullptr, "ResumeApplicationDownload"},
+ {35, nullptr, "UpdateVersionList"},
+ {36, nullptr, "PushLaunchVersion"},
+ {37, nullptr, "ListRequiredVersion"},
+ {38, nullptr, "CheckApplicationLaunchVersion"},
+ {39, nullptr, "CheckApplicationLaunchRights"},
+ {40, nullptr, "GetApplicationLogoData"},
+ {41, nullptr, "CalculateApplicationDownloadRequiredSize"},
+ {42, nullptr, "CleanupSdCard"},
+ {43, nullptr, "CheckSdCardMountStatus"},
+ {44, nullptr, "GetSdCardMountStatusChangedEvent"},
+ {45, nullptr, "GetGameCardAttachmentEvent"},
+ {46, nullptr, "GetGameCardAttachmentInfo"},
+ {47, nullptr, "GetTotalSpaceSize"},
+ {48, nullptr, "GetFreeSpaceSize"},
+ {49, nullptr, "GetSdCardRemovedEvent"},
+ {52, nullptr, "GetGameCardUpdateDetectionEvent"},
+ {53, nullptr, "DisableApplicationAutoDelete"},
+ {54, nullptr, "EnableApplicationAutoDelete"},
+ {55, &IApplicationManagerInterface::GetApplicationDesiredLanguage, "GetApplicationDesiredLanguage"},
+ {56, nullptr, "SetApplicationTerminateResult"},
+ {57, nullptr, "ClearApplicationTerminateResult"},
+ {58, nullptr, "GetLastSdCardMountUnexpectedResult"},
+ {59, &IApplicationManagerInterface::ConvertApplicationLanguageToLanguageCode, "ConvertApplicationLanguageToLanguageCode"},
+ {60, nullptr, "ConvertLanguageCodeToApplicationLanguage"},
+ {61, nullptr, "GetBackgroundDownloadStressTaskInfo"},
+ {62, nullptr, "GetGameCardStopper"},
+ {63, nullptr, "IsSystemProgramInstalled"},
+ {64, nullptr, "StartApplyDeltaTask"},
+ {65, nullptr, "GetRequestServerStopper"},
+ {66, nullptr, "GetBackgroundApplyDeltaStressTaskInfo"},
+ {67, nullptr, "CancelApplicationApplyDelta"},
+ {68, nullptr, "ResumeApplicationApplyDelta"},
+ {69, nullptr, "CalculateApplicationApplyDeltaRequiredSize"},
+ {70, nullptr, "ResumeAll"},
+ {71, nullptr, "GetStorageSize"},
+ {80, nullptr, "RequestDownloadApplication"},
+ {81, nullptr, "RequestDownloadAddOnContent"},
+ {82, nullptr, "DownloadApplication"},
+ {83, nullptr, "CheckApplicationResumeRights"},
+ {84, nullptr, "GetDynamicCommitEvent"},
+ {85, nullptr, "RequestUpdateApplication2"},
+ {86, nullptr, "EnableApplicationCrashReport"},
+ {87, nullptr, "IsApplicationCrashReportEnabled"},
+ {90, nullptr, "BoostSystemMemoryResourceLimit"},
+ {91, nullptr, "DeprecatedLaunchApplication"},
+ {92, nullptr, "GetRunningApplicationProgramId"},
+ {93, nullptr, "GetMainApplicationProgramIndex"},
+ {94, nullptr, "LaunchApplication"},
+ {95, nullptr, "GetApplicationLaunchInfo"},
+ {96, nullptr, "AcquireApplicationLaunchInfo"},
+ {97, nullptr, "GetMainApplicationProgramIndex2"},
+ {98, nullptr, "EnableApplicationAllThreadDumpOnCrash"},
+ {100, nullptr, "ResetToFactorySettings"},
+ {101, nullptr, "ResetToFactorySettingsWithoutUserSaveData"},
+ {102, nullptr, "ResetToFactorySettingsForRefurbishment"},
+ {200, nullptr, "CalculateUserSaveDataStatistics"},
+ {201, nullptr, "DeleteUserSaveDataAll"},
+ {210, nullptr, "DeleteUserSystemSaveData"},
+ {211, nullptr, "DeleteSaveData"},
+ {220, nullptr, "UnregisterNetworkServiceAccount"},
+ {221, nullptr, "UnregisterNetworkServiceAccountWithUserSaveDataDeletion"},
+ {300, nullptr, "GetApplicationShellEvent"},
+ {301, nullptr, "PopApplicationShellEventInfo"},
+ {302, nullptr, "LaunchLibraryApplet"},
+ {303, nullptr, "TerminateLibraryApplet"},
+ {304, nullptr, "LaunchSystemApplet"},
+ {305, nullptr, "TerminateSystemApplet"},
+ {306, nullptr, "LaunchOverlayApplet"},
+ {307, nullptr, "TerminateOverlayApplet"},
+ {400, &IApplicationManagerInterface::GetApplicationControlData, "GetApplicationControlData"},
+ {401, nullptr, "InvalidateAllApplicationControlCache"},
+ {402, nullptr, "RequestDownloadApplicationControlData"},
+ {403, nullptr, "GetMaxApplicationControlCacheCount"},
+ {404, nullptr, "InvalidateApplicationControlCache"},
+ {405, nullptr, "ListApplicationControlCacheEntryInfo"},
+ {406, nullptr, "GetApplicationControlProperty"},
+ {502, nullptr, "RequestCheckGameCardRegistration"},
+ {503, nullptr, "RequestGameCardRegistrationGoldPoint"},
+ {504, nullptr, "RequestRegisterGameCard"},
+ {505, nullptr, "GetGameCardMountFailureEvent"},
+ {506, nullptr, "IsGameCardInserted"},
+ {507, nullptr, "EnsureGameCardAccess"},
+ {508, nullptr, "GetLastGameCardMountFailureResult"},
+ {509, nullptr, "ListApplicationIdOnGameCard"},
+ {600, nullptr, "CountApplicationContentMeta"},
+ {601, nullptr, "ListApplicationContentMetaStatus"},
+ {602, nullptr, "ListAvailableAddOnContent"},
+ {603, nullptr, "GetOwnedApplicationContentMetaStatus"},
+ {604, nullptr, "RegisterContentsExternalKey"},
+ {605, nullptr, "ListApplicationContentMetaStatusWithRightsCheck"},
+ {606, nullptr, "GetContentMetaStorage"},
+ {607, nullptr, "ListAvailableAddOnContent"},
+ {700, nullptr, "PushDownloadTaskList"},
+ {701, nullptr, "ClearTaskStatusList"},
+ {702, nullptr, "RequestDownloadTaskList"},
+ {703, nullptr, "RequestEnsureDownloadTask"},
+ {704, nullptr, "ListDownloadTaskStatus"},
+ {705, nullptr, "RequestDownloadTaskListData"},
+ {800, nullptr, "RequestVersionList"},
+ {801, nullptr, "ListVersionList"},
+ {802, nullptr, "RequestVersionListData"},
+ {900, nullptr, "GetApplicationRecord"},
+ {901, nullptr, "GetApplicationRecordProperty"},
+ {902, nullptr, "EnableApplicationAutoUpdate"},
+ {903, nullptr, "DisableApplicationAutoUpdate"},
+ {904, nullptr, "TouchApplication"},
+ {905, nullptr, "RequestApplicationUpdate"},
+ {906, nullptr, "IsApplicationUpdateRequested"},
+ {907, nullptr, "WithdrawApplicationUpdateRequest"},
+ {908, nullptr, "ListApplicationRecordInstalledContentMeta"},
+ {909, nullptr, "WithdrawCleanupAddOnContentsWithNoRightsRecommendation"},
+ {910, nullptr, "HasApplicationRecord"},
+ {911, nullptr, "SetPreInstalledApplication"},
+ {912, nullptr, "ClearPreInstalledApplicationFlag"},
+ {1000, nullptr, "RequestVerifyApplicationDeprecated"},
+ {1001, nullptr, "CorruptApplicationForDebug"},
+ {1002, nullptr, "RequestVerifyAddOnContentsRights"},
+ {1003, nullptr, "RequestVerifyApplication"},
+ {1004, nullptr, "CorruptContentForDebug"},
+ {1200, nullptr, "NeedsUpdateVulnerability"},
+ {1300, nullptr, "IsAnyApplicationEntityInstalled"},
+ {1301, nullptr, "DeleteApplicationContentEntities"},
+ {1302, nullptr, "CleanupUnrecordedApplicationEntity"},
+ {1303, nullptr, "CleanupAddOnContentsWithNoRights"},
+ {1304, nullptr, "DeleteApplicationContentEntity"},
+ {1305, nullptr, "TryDeleteRunningApplicationEntity"},
+ {1306, nullptr, "TryDeleteRunningApplicationCompletely"},
+ {1307, nullptr, "TryDeleteRunningApplicationContentEntities"},
+ {1308, nullptr, "DeleteApplicationCompletelyForDebug"},
+ {1309, nullptr, "CleanupUnavailableAddOnContents"},
+ {1400, nullptr, "PrepareShutdown"},
+ {1500, nullptr, "FormatSdCard"},
+ {1501, nullptr, "NeedsSystemUpdateToFormatSdCard"},
+ {1502, nullptr, "GetLastSdCardFormatUnexpectedResult"},
+ {1504, nullptr, "InsertSdCard"},
+ {1505, nullptr, "RemoveSdCard"},
+ {1600, nullptr, "GetSystemSeedForPseudoDeviceId"},
+ {1601, nullptr, "ResetSystemSeedForPseudoDeviceId"},
+ {1700, nullptr, "ListApplicationDownloadingContentMeta"},
+ {1701, nullptr, "GetApplicationView"},
+ {1702, nullptr, "GetApplicationDownloadTaskStatus"},
+ {1703, nullptr, "GetApplicationViewDownloadErrorContext"},
+ {1800, nullptr, "IsNotificationSetupCompleted"},
+ {1801, nullptr, "GetLastNotificationInfoCount"},
+ {1802, nullptr, "ListLastNotificationInfo"},
+ {1803, nullptr, "ListNotificationTask"},
+ {1900, nullptr, "IsActiveAccount"},
+ {1901, nullptr, "RequestDownloadApplicationPrepurchasedRights"},
+ {1902, nullptr, "GetApplicationTicketInfo"},
+ {2000, nullptr, "GetSystemDeliveryInfo"},
+ {2001, nullptr, "SelectLatestSystemDeliveryInfo"},
+ {2002, nullptr, "VerifyDeliveryProtocolVersion"},
+ {2003, nullptr, "GetApplicationDeliveryInfo"},
+ {2004, nullptr, "HasAllContentsToDeliver"},
+ {2005, nullptr, "CompareApplicationDeliveryInfo"},
+ {2006, nullptr, "CanDeliverApplication"},
+ {2007, nullptr, "ListContentMetaKeyToDeliverApplication"},
+ {2008, nullptr, "NeedsSystemUpdateToDeliverApplication"},
+ {2009, nullptr, "EstimateRequiredSize"},
+ {2010, nullptr, "RequestReceiveApplication"},
+ {2011, nullptr, "CommitReceiveApplication"},
+ {2012, nullptr, "GetReceiveApplicationProgress"},
+ {2013, nullptr, "RequestSendApplication"},
+ {2014, nullptr, "GetSendApplicationProgress"},
+ {2015, nullptr, "CompareSystemDeliveryInfo"},
+ {2016, nullptr, "ListNotCommittedContentMeta"},
+ {2017, nullptr, "CreateDownloadTask"},
+ {2018, nullptr, "GetApplicationDeliveryInfoHash"},
+ {2050, nullptr, "GetApplicationRightsOnClient"},
+ {2100, nullptr, "GetApplicationTerminateResult"},
+ {2101, nullptr, "GetRawApplicationTerminateResult"},
+ {2150, nullptr, "CreateRightsEnvironment"},
+ {2151, nullptr, "DestroyRightsEnvironment"},
+ {2152, nullptr, "ActivateRightsEnvironment"},
+ {2153, nullptr, "DeactivateRightsEnvironment"},
+ {2154, nullptr, "ForceActivateRightsContextForExit"},
+ {2160, nullptr, "AddTargetApplicationToRightsEnvironment"},
+ {2161, nullptr, "SetUsersToRightsEnvironment"},
+ {2170, nullptr, "GetRightsEnvironmentStatus"},
+ {2171, nullptr, "GetRightsEnvironmentStatusChangedEvent"},
+ {2180, nullptr, "RequestExtendRightsInRightsEnvironment"},
+ {2181, nullptr, "GetLastResultOfExtendRightsInRightsEnvironment"},
+ {2182, nullptr, "SetActiveRightsContextUsingStateToRightsEnvironment"},
+ {2190, nullptr, "GetRightsEnvironmentHandleForApplication"},
+ {2199, nullptr, "GetRightsEnvironmentCountForDebug"},
+ {2200, nullptr, "GetGameCardApplicationCopyIdentifier"},
+ {2201, nullptr, "GetInstalledApplicationCopyIdentifier"},
+ {2250, nullptr, "RequestReportActiveELicence"},
+ {2300, nullptr, "ListEventLog"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+}
- RegisterHandlers(functions);
- }
+IApplicationManagerInterface::~IApplicationManagerInterface() = default;
+
+void IApplicationManagerInterface::GetApplicationControlData(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto flag = rp.PopRaw<u64>();
+ LOG_DEBUG(Service_NS, "called with flag={:016X}", flag);
- void GetApplicationControlData(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto flag = rp.PopRaw<u64>();
- LOG_DEBUG(Service_NS, "called with flag={:016X}", flag);
-
- const auto title_id = rp.PopRaw<u64>();
-
- const auto size = ctx.GetWriteBufferSize();
-
- const FileSys::PatchManager pm{title_id};
- const auto control = pm.GetControlMetadata();
-
- std::vector<u8> out;
-
- if (control.first != nullptr) {
- if (size < 0x4000) {
- LOG_ERROR(Service_NS,
- "output buffer is too small! (actual={:016X}, expected_min=0x4000)",
- size);
- IPC::ResponseBuilder rb{ctx, 2};
- // TODO(DarkLordZach): Find a better error code for this.
- rb.Push(ResultCode(-1));
- return;
- }
-
- out.resize(0x4000);
- const auto bytes = control.first->GetRawBytes();
- std::memcpy(out.data(), bytes.data(), bytes.size());
- } else {
- LOG_WARNING(Service_NS, "missing NACP data for title_id={:016X}, defaulting to zeros.",
- title_id);
- out.resize(std::min<u64>(0x4000, size));
+ const auto title_id = rp.PopRaw<u64>();
+
+ const auto size = ctx.GetWriteBufferSize();
+
+ const FileSys::PatchManager pm{title_id};
+ const auto control = pm.GetControlMetadata();
+
+ std::vector<u8> out;
+
+ if (control.first != nullptr) {
+ if (size < 0x4000) {
+ LOG_ERROR(Service_NS,
+ "output buffer is too small! (actual={:016X}, expected_min=0x4000)", size);
+ IPC::ResponseBuilder rb{ctx, 2};
+ // TODO(DarkLordZach): Find a better error code for this.
+ rb.Push(ResultCode(-1));
+ return;
}
- if (control.second != nullptr) {
- if (size < 0x4000 + control.second->GetSize()) {
- LOG_ERROR(Service_NS,
- "output buffer is too small! (actual={:016X}, expected_min={:016X})",
- size, 0x4000 + control.second->GetSize());
- IPC::ResponseBuilder rb{ctx, 2};
- // TODO(DarkLordZach): Find a better error code for this.
- rb.Push(ResultCode(-1));
- return;
- }
-
- out.resize(0x4000 + control.second->GetSize());
- control.second->Read(out.data() + 0x4000, control.second->GetSize());
- } else {
- LOG_WARNING(Service_NS, "missing icon data for title_id={:016X}, defaulting to zeros.",
- title_id);
+ out.resize(0x4000);
+ const auto bytes = control.first->GetRawBytes();
+ std::memcpy(out.data(), bytes.data(), bytes.size());
+ } else {
+ LOG_WARNING(Service_NS, "missing NACP data for title_id={:016X}, defaulting to zeros.",
+ title_id);
+ out.resize(std::min<u64>(0x4000, size));
+ }
+
+ if (control.second != nullptr) {
+ if (size < 0x4000 + control.second->GetSize()) {
+ LOG_ERROR(Service_NS,
+ "output buffer is too small! (actual={:016X}, expected_min={:016X})", size,
+ 0x4000 + control.second->GetSize());
+ IPC::ResponseBuilder rb{ctx, 2};
+ // TODO(DarkLordZach): Find a better error code for this.
+ rb.Push(ResultCode(-1));
+ return;
}
- ctx.WriteBuffer(out);
+ out.resize(0x4000 + control.second->GetSize());
+ control.second->Read(out.data() + 0x4000, control.second->GetSize());
+ } else {
+ LOG_WARNING(Service_NS, "missing icon data for title_id={:016X}, defaulting to zeros.",
+ title_id);
+ }
+
+ ctx.WriteBuffer(out);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(static_cast<u32>(out.size()));
+}
+
+void IApplicationManagerInterface::GetApplicationDesiredLanguage(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto supported_languages = rp.Pop<u32>();
+ const auto res = GetApplicationDesiredLanguage(supported_languages);
+ if (res.Succeeded()) {
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(static_cast<u32>(out.size()));
+ rb.Push<u32>(*res);
+ } else {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(res.Code());
}
-};
+}
-class IApplicationVersionInterface final : public ServiceFramework<IApplicationVersionInterface> {
-public:
- explicit IApplicationVersionInterface() : ServiceFramework{"IApplicationVersionInterface"} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {0, nullptr, "GetLaunchRequiredVersion"},
- {1, nullptr, "UpgradeLaunchRequiredVersion"},
- {35, nullptr, "UpdateVersionList"},
- {36, nullptr, "PushLaunchVersion"},
- {37, nullptr, "ListRequiredVersion"},
- {800, nullptr, "RequestVersionList"},
- {801, nullptr, "ListVersionList"},
- {802, nullptr, "RequestVersionListData"},
- {1000, nullptr, "PerformAutoUpdate"},
- };
- // clang-format on
+ResultVal<u8> IApplicationManagerInterface::GetApplicationDesiredLanguage(
+ const u32 supported_languages) {
+ LOG_DEBUG(Service_NS, "called with supported_languages={:08X}", supported_languages);
- RegisterHandlers(functions);
- }
-};
+ // Get language code from settings
+ const auto language_code = Set::GetLanguageCodeFromIndex(Settings::values.language_index);
-class IContentManagerInterface final : public ServiceFramework<IContentManagerInterface> {
-public:
- explicit IContentManagerInterface() : ServiceFramework{"IContentManagerInterface"} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {11, nullptr, "CalculateApplicationOccupiedSize"},
- {43, nullptr, "CheckSdCardMountStatus"},
- {47, nullptr, "GetTotalSpaceSize"},
- {48, nullptr, "GetFreeSpaceSize"},
- {600, nullptr, "CountApplicationContentMeta"},
- {601, nullptr, "ListApplicationContentMetaStatus"},
- {605, nullptr, "ListApplicationContentMetaStatusWithRightsCheck"},
- {607, nullptr, "IsAnyApplicationRunning"},
- };
- // clang-format on
+ // Convert to application language, get priority list
+ const auto application_language = ConvertToApplicationLanguage(language_code);
+ if (application_language == std::nullopt) {
+ return ERR_APPLICATION_LANGUAGE_NOT_FOUND;
+ }
+ const auto priority_list = GetApplicationLanguagePriorityList(*application_language);
+ if (!priority_list) {
+ return ERR_APPLICATION_LANGUAGE_NOT_FOUND;
+ }
- RegisterHandlers(functions);
+ // Try to find a valid language.
+ for (const auto lang : *priority_list) {
+ const auto supported_flag = GetSupportedLanguageFlag(lang);
+ if (supported_languages == 0 || (supported_languages & supported_flag) == supported_flag) {
+ return MakeResult(static_cast<u8>(lang));
+ }
}
-};
-class IDocumentInterface final : public ServiceFramework<IDocumentInterface> {
-public:
- explicit IDocumentInterface() : ServiceFramework{"IDocumentInterface"} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {21, nullptr, "GetApplicationContentPath"},
- {23, nullptr, "ResolveApplicationContentPath"},
- {93, nullptr, "GetRunningApplicationProgramId"},
- };
- // clang-format on
+ return ERR_APPLICATION_LANGUAGE_NOT_FOUND;
+}
- RegisterHandlers(functions);
- }
-};
+void IApplicationManagerInterface::ConvertApplicationLanguageToLanguageCode(
+ Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto application_language = rp.Pop<u8>();
-class IDownloadTaskInterface final : public ServiceFramework<IDownloadTaskInterface> {
-public:
- explicit IDownloadTaskInterface() : ServiceFramework{"IDownloadTaskInterface"} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {701, nullptr, "ClearTaskStatusList"},
- {702, nullptr, "RequestDownloadTaskList"},
- {703, nullptr, "RequestEnsureDownloadTask"},
- {704, nullptr, "ListDownloadTaskStatus"},
- {705, nullptr, "RequestDownloadTaskListData"},
- {706, nullptr, "TryCommitCurrentApplicationDownloadTask"},
- {707, nullptr, "EnableAutoCommit"},
- {708, nullptr, "DisableAutoCommit"},
- {709, nullptr, "TriggerDynamicCommitEvent"},
- };
- // clang-format on
+ const auto res = ConvertApplicationLanguageToLanguageCode(application_language);
+ if (res.Succeeded()) {
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(*res);
+ } else {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(res.Code());
+ }
+}
- RegisterHandlers(functions);
+ResultVal<u64> IApplicationManagerInterface::ConvertApplicationLanguageToLanguageCode(
+ u8 application_language) {
+ const auto language_code =
+ ConvertToLanguageCode(static_cast<ApplicationLanguage>(application_language));
+ if (language_code == std::nullopt) {
+ return ERR_APPLICATION_LANGUAGE_NOT_FOUND;
}
-};
-class IECommerceInterface final : public ServiceFramework<IECommerceInterface> {
-public:
- explicit IECommerceInterface() : ServiceFramework{"IECommerceInterface"} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {0, nullptr, "RequestLinkDevice"},
- {1, nullptr, "RequestCleanupAllPreInstalledApplications"},
- {2, nullptr, "RequestCleanupPreInstalledApplication"},
- {3, nullptr, "RequestSyncRights"},
- {4, nullptr, "RequestUnlinkDevice"},
- {5, nullptr, "RequestRevokeAllELicense"},
- };
- // clang-format on
+ return MakeResult(static_cast<u64>(*language_code));
+}
- RegisterHandlers(functions);
- }
-};
+IApplicationVersionInterface::IApplicationVersionInterface()
+ : ServiceFramework{"IApplicationVersionInterface"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "GetLaunchRequiredVersion"},
+ {1, nullptr, "UpgradeLaunchRequiredVersion"},
+ {35, nullptr, "UpdateVersionList"},
+ {36, nullptr, "PushLaunchVersion"},
+ {37, nullptr, "ListRequiredVersion"},
+ {800, nullptr, "RequestVersionList"},
+ {801, nullptr, "ListVersionList"},
+ {802, nullptr, "RequestVersionListData"},
+ {1000, nullptr, "PerformAutoUpdate"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+}
-class IFactoryResetInterface final : public ServiceFramework<IFactoryResetInterface> {
-public:
- explicit IFactoryResetInterface() : ServiceFramework{"IFactoryResetInterface"} {
- // clang-format off
+IApplicationVersionInterface::~IApplicationVersionInterface() = default;
+
+IContentManagerInterface::IContentManagerInterface()
+ : ServiceFramework{"IContentManagerInterface"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {11, nullptr, "CalculateApplicationOccupiedSize"},
+ {43, nullptr, "CheckSdCardMountStatus"},
+ {47, nullptr, "GetTotalSpaceSize"},
+ {48, nullptr, "GetFreeSpaceSize"},
+ {600, nullptr, "CountApplicationContentMeta"},
+ {601, nullptr, "ListApplicationContentMetaStatus"},
+ {605, nullptr, "ListApplicationContentMetaStatusWithRightsCheck"},
+ {607, nullptr, "IsAnyApplicationRunning"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+}
+
+IContentManagerInterface::~IContentManagerInterface() = default;
+
+IDocumentInterface::IDocumentInterface() : ServiceFramework{"IDocumentInterface"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {21, nullptr, "GetApplicationContentPath"},
+ {23, nullptr, "ResolveApplicationContentPath"},
+ {93, nullptr, "GetRunningApplicationProgramId"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+}
+
+IDocumentInterface::~IDocumentInterface() = default;
+
+IDownloadTaskInterface::IDownloadTaskInterface() : ServiceFramework{"IDownloadTaskInterface"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {701, nullptr, "ClearTaskStatusList"},
+ {702, nullptr, "RequestDownloadTaskList"},
+ {703, nullptr, "RequestEnsureDownloadTask"},
+ {704, nullptr, "ListDownloadTaskStatus"},
+ {705, nullptr, "RequestDownloadTaskListData"},
+ {706, nullptr, "TryCommitCurrentApplicationDownloadTask"},
+ {707, nullptr, "EnableAutoCommit"},
+ {708, nullptr, "DisableAutoCommit"},
+ {709, nullptr, "TriggerDynamicCommitEvent"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+}
+
+IDownloadTaskInterface::~IDownloadTaskInterface() = default;
+
+IECommerceInterface::IECommerceInterface() : ServiceFramework{"IECommerceInterface"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "RequestLinkDevice"},
+ {1, nullptr, "RequestCleanupAllPreInstalledApplications"},
+ {2, nullptr, "RequestCleanupPreInstalledApplication"},
+ {3, nullptr, "RequestSyncRights"},
+ {4, nullptr, "RequestUnlinkDevice"},
+ {5, nullptr, "RequestRevokeAllELicense"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+}
+
+IECommerceInterface::~IECommerceInterface() = default;
+
+IFactoryResetInterface::IFactoryResetInterface::IFactoryResetInterface()
+ : ServiceFramework{"IFactoryResetInterface"} {
+ // clang-format off
static const FunctionInfo functions[] = {
{100, nullptr, "ResetToFactorySettings"},
{101, nullptr, "ResetToFactorySettingsWithoutUserSaveData"},
{102, nullptr, "ResetToFactorySettingsForRefurbishment"},
};
- // clang-format on
+ // clang-format on
- RegisterHandlers(functions);
- }
-};
-
-class NS final : public ServiceFramework<NS> {
-public:
- explicit NS(const char* name) : ServiceFramework{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"},
- {7997, &NS::PushInterface<IDownloadTaskInterface>, "GetDownloadTaskInterface"},
- {7998, &NS::PushInterface<IContentManagerInterface>, "GetContentManagementInterface"},
- {7999, &NS::PushInterface<IDocumentInterface>, "GetDocumentInterface"},
- };
- // clang-format on
+ RegisterHandlers(functions);
+}
- RegisterHandlers(functions);
- }
+IFactoryResetInterface::~IFactoryResetInterface() = default;
+
+NS::NS(const char* name) : ServiceFramework{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"},
+ {7997, &NS::PushInterface<IDownloadTaskInterface>, "GetDownloadTaskInterface"},
+ {7998, &NS::PushInterface<IContentManagerInterface>, "GetContentManagementInterface"},
+ {7999, &NS::PushInterface<IDocumentInterface>, "GetDocumentInterface"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+}
-private:
- template <typename T>
- void PushInterface(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_NS, "called");
+NS::~NS() = default;
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<T>();
- }
-};
+std::shared_ptr<IApplicationManagerInterface> NS::GetApplicationManagerInterface() const {
+ return GetInterface<IApplicationManagerInterface>();
+}
class NS_DEV final : public ServiceFramework<NS_DEV> {
public:
diff --git a/src/core/hle/service/ns/ns.h b/src/core/hle/service/ns/ns.h
index b81ca8f1e..0e8256cb4 100644
--- a/src/core/hle/service/ns/ns.h
+++ b/src/core/hle/service/ns/ns.h
@@ -8,6 +8,88 @@
namespace Service::NS {
+class IAccountProxyInterface final : public ServiceFramework<IAccountProxyInterface> {
+public:
+ explicit IAccountProxyInterface();
+ ~IAccountProxyInterface() override;
+};
+
+class IApplicationManagerInterface final : public ServiceFramework<IApplicationManagerInterface> {
+public:
+ explicit IApplicationManagerInterface();
+ ~IApplicationManagerInterface() override;
+
+ ResultVal<u8> GetApplicationDesiredLanguage(u32 supported_languages);
+ ResultVal<u64> ConvertApplicationLanguageToLanguageCode(u8 application_language);
+
+private:
+ void GetApplicationControlData(Kernel::HLERequestContext& ctx);
+ void GetApplicationDesiredLanguage(Kernel::HLERequestContext& ctx);
+ void ConvertApplicationLanguageToLanguageCode(Kernel::HLERequestContext& ctx);
+};
+
+class IApplicationVersionInterface final : public ServiceFramework<IApplicationVersionInterface> {
+public:
+ explicit IApplicationVersionInterface();
+ ~IApplicationVersionInterface() override;
+};
+
+class IContentManagerInterface final : public ServiceFramework<IContentManagerInterface> {
+public:
+ explicit IContentManagerInterface();
+ ~IContentManagerInterface() override;
+};
+
+class IDocumentInterface final : public ServiceFramework<IDocumentInterface> {
+public:
+ explicit IDocumentInterface();
+ ~IDocumentInterface() override;
+};
+
+class IDownloadTaskInterface final : public ServiceFramework<IDownloadTaskInterface> {
+public:
+ explicit IDownloadTaskInterface();
+ ~IDownloadTaskInterface() override;
+};
+
+class IECommerceInterface final : public ServiceFramework<IECommerceInterface> {
+public:
+ explicit IECommerceInterface();
+ ~IECommerceInterface() override;
+};
+
+class IFactoryResetInterface final : public ServiceFramework<IFactoryResetInterface> {
+public:
+ explicit IFactoryResetInterface();
+ ~IFactoryResetInterface() override;
+};
+
+class NS final : public ServiceFramework<NS> {
+public:
+ explicit NS(const char* name);
+ ~NS() override;
+
+ std::shared_ptr<IApplicationManagerInterface> GetApplicationManagerInterface() const;
+
+private:
+ template <typename T>
+ void PushInterface(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_NS, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<T>();
+ }
+
+ template <typename T>
+ std::shared_ptr<T> GetInterface() const {
+ static_assert(std::is_base_of_v<Kernel::SessionRequestHandler, T>,
+ "Not a base of ServiceFrameworkBase");
+
+ return std::make_shared<T>();
+ }
+};
+
/// Registers all NS services with the specified service manager.
void InstallInterfaces(SM::ServiceManager& service_manager);
diff --git a/src/core/hle/service/ns/ns_language.h b/src/core/hle/service/ns/ns_language.h
new file mode 100644
index 000000000..59ac85a19
--- /dev/null
+++ b/src/core/hle/service/ns/ns_language.h
@@ -0,0 +1,42 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+#include <optional>
+#include <string>
+#include "common/common_types.h"
+#include "core/hle/service/set/set.h"
+
+namespace Service::NS {
+/// This is nn::ns::detail::ApplicationLanguage
+enum class ApplicationLanguage : u8 {
+ AmericanEnglish = 0,
+ BritishEnglish,
+ Japanese,
+ French,
+ German,
+ LatinAmericanSpanish,
+ Spanish,
+ Italian,
+ Dutch,
+ CanadianFrench,
+ Portuguese,
+ Russian,
+ Korean,
+ TraditionalChinese,
+ SimplifiedChinese,
+ Count
+};
+using ApplicationLanguagePriorityList =
+ const std::array<ApplicationLanguage, static_cast<std::size_t>(ApplicationLanguage::Count)>;
+
+constexpr u32 GetSupportedLanguageFlag(const ApplicationLanguage lang) {
+ return 1U << static_cast<u32>(lang);
+}
+
+const ApplicationLanguagePriorityList* GetApplicationLanguagePriorityList(ApplicationLanguage lang);
+std::optional<ApplicationLanguage> ConvertToApplicationLanguage(
+ Service::Set::LanguageCode language_code);
+std::optional<Service::Set::LanguageCode> ConvertToLanguageCode(ApplicationLanguage lang);
+} // namespace Service::NS \ No newline at end of file
diff --git a/src/core/hle/service/nvdrv/devices/nvdevice.h b/src/core/hle/service/nvdrv/devices/nvdevice.h
index 0f02a1a18..4f6042b00 100644
--- a/src/core/hle/service/nvdrv/devices/nvdevice.h
+++ b/src/core/hle/service/nvdrv/devices/nvdevice.h
@@ -19,11 +19,11 @@ public:
virtual ~nvdevice() = default;
union Ioctl {
u32_le raw;
- BitField<0, 8, u32_le> cmd;
- BitField<8, 8, u32_le> group;
- BitField<16, 14, u32_le> length;
- BitField<30, 1, u32_le> is_in;
- BitField<31, 1, u32_le> is_out;
+ 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;
};
/**
diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h
index ace71169f..12f3ef825 100644
--- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h
+++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h
@@ -18,7 +18,7 @@ class nvmap;
class nvdisp_disp0 final : public nvdevice {
public:
explicit nvdisp_disp0(std::shared_ptr<nvmap> nvmap_dev);
- ~nvdisp_disp0();
+ ~nvdisp_disp0() override;
u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override;
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 a34b9e753..af62d33d2 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
@@ -10,6 +10,7 @@
#include "core/core.h"
#include "core/hle/service/nvdrv/devices/nvhost_as_gpu.h"
#include "core/hle/service/nvdrv/devices/nvmap.h"
+#include "core/memory.h"
#include "video_core/memory_manager.h"
#include "video_core/rasterizer_interface.h"
#include "video_core/renderer_base.h"
@@ -88,7 +89,7 @@ u32 nvhost_as_gpu::Remap(const std::vector<u8>& input, std::vector<u8>& output)
for (const auto& entry : entries) {
LOG_WARNING(Service_NVDRV, "remap entry, offset=0x{:X} handle=0x{:X} pages=0x{:X}",
entry.offset, entry.nvmap_handle, entry.pages);
- Tegra::GPUVAddr offset = static_cast<Tegra::GPUVAddr>(entry.offset) << 0x10;
+ GPUVAddr offset = static_cast<GPUVAddr>(entry.offset) << 0x10;
auto object = nvmap_dev->GetObject(entry.nvmap_handle);
if (!object) {
LOG_CRITICAL(Service_NVDRV, "nvmap {} is an invalid handle!", entry.nvmap_handle);
@@ -101,7 +102,7 @@ u32 nvhost_as_gpu::Remap(const std::vector<u8>& input, std::vector<u8>& output)
u64 size = static_cast<u64>(entry.pages) << 0x10;
ASSERT(size <= object->size);
- Tegra::GPUVAddr returned = gpu.MemoryManager().MapBufferEx(object->addr, offset, size);
+ GPUVAddr returned = gpu.MemoryManager().MapBufferEx(object->addr, offset, size);
ASSERT(returned == offset);
}
std::memcpy(output.data(), entries.data(), output.size());
@@ -172,16 +173,8 @@ u32 nvhost_as_gpu::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& ou
return 0;
}
- auto& system_instance = Core::System::GetInstance();
-
- // Remove this memory region from the rasterizer cache.
- auto& gpu = system_instance.GPU();
- auto cpu_addr = gpu.MemoryManager().GpuToCpuAddress(params.offset);
- ASSERT(cpu_addr);
- gpu.FlushAndInvalidateRegion(*cpu_addr, itr->second.size);
-
- params.offset = gpu.MemoryManager().UnmapBuffer(params.offset, itr->second.size);
-
+ params.offset = Core::System::GetInstance().GPU().MemoryManager().UnmapBuffer(params.offset,
+ itr->second.size);
buffer_mappings.erase(itr->second.offset);
std::memcpy(output.data(), &params, output.size());
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 45812d238..0e28755bd 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp
@@ -185,7 +185,8 @@ u32 nvhost_ctrl_gpu::GetGpuTime(const std::vector<u8>& input, std::vector<u8>& o
IoctlGetGpuTime params{};
std::memcpy(&params, input.data(), input.size());
- params.gpu_time = Core::Timing::cyclesToNs(Core::System::GetInstance().CoreTiming().GetTicks());
+ const auto ns = Core::Timing::CyclesToNs(Core::System::GetInstance().CoreTiming().GetTicks());
+ params.gpu_time = static_cast<u64_le>(ns.count());
std::memcpy(output.data(), &params, output.size());
return 0;
}
diff --git a/src/core/hle/service/nvdrv/interface.cpp b/src/core/hle/service/nvdrv/interface.cpp
index 3b9ab4b14..b60fc748b 100644
--- a/src/core/hle/service/nvdrv/interface.cpp
+++ b/src/core/hle/service/nvdrv/interface.cpp
@@ -129,7 +129,7 @@ NVDRV::NVDRV(std::shared_ptr<Module> nvdrv, const char* name)
RegisterHandlers(functions);
auto& kernel = Core::System::GetInstance().Kernel();
- query_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::OneShot,
+ query_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Automatic,
"NVDRV::query_event");
}
diff --git a/src/core/hle/service/nvdrv/interface.h b/src/core/hle/service/nvdrv/interface.h
index fe311b069..5b4889910 100644
--- a/src/core/hle/service/nvdrv/interface.h
+++ b/src/core/hle/service/nvdrv/interface.h
@@ -17,7 +17,7 @@ namespace Service::Nvidia {
class NVDRV final : public ServiceFramework<NVDRV> {
public:
NVDRV(std::shared_ptr<Module> nvdrv, const char* name);
- ~NVDRV();
+ ~NVDRV() override;
private:
void Open(Kernel::HLERequestContext& ctx);
diff --git a/src/core/hle/service/nvdrv/nvmemp.h b/src/core/hle/service/nvdrv/nvmemp.h
index 5a4dfc1f9..6eafb1346 100644
--- a/src/core/hle/service/nvdrv/nvmemp.h
+++ b/src/core/hle/service/nvdrv/nvmemp.h
@@ -11,7 +11,7 @@ namespace Service::Nvidia {
class NVMEMP final : public ServiceFramework<NVMEMP> {
public:
NVMEMP();
- ~NVMEMP();
+ ~NVMEMP() override;
private:
void Cmd0(Kernel::HLERequestContext& ctx);
diff --git a/src/core/hle/service/nvflinger/buffer_queue.cpp b/src/core/hle/service/nvflinger/buffer_queue.cpp
index 4d150fc71..5731e815f 100644
--- a/src/core/hle/service/nvflinger/buffer_queue.cpp
+++ b/src/core/hle/service/nvflinger/buffer_queue.cpp
@@ -16,7 +16,7 @@ namespace Service::NVFlinger {
BufferQueue::BufferQueue(u32 id, u64 layer_id) : id(id), layer_id(layer_id) {
auto& kernel = Core::System::GetInstance().Kernel();
- buffer_wait_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Sticky,
+ buffer_wait_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Manual,
"BufferQueue NativeHandle");
}
diff --git a/src/core/hle/service/nvflinger/nvflinger.cpp b/src/core/hle/service/nvflinger/nvflinger.cpp
index fc496b654..3c5c53e24 100644
--- a/src/core/hle/service/nvflinger/nvflinger.cpp
+++ b/src/core/hle/service/nvflinger/nvflinger.cpp
@@ -21,12 +21,13 @@
#include "core/hle/service/vi/display/vi_display.h"
#include "core/hle/service/vi/layer/vi_layer.h"
#include "core/perf_stats.h"
+#include "core/settings.h"
#include "video_core/renderer_base.h"
namespace Service::NVFlinger {
-constexpr std::size_t SCREEN_REFRESH_RATE = 60;
-constexpr u64 frame_ticks = static_cast<u64>(Core::Timing::BASE_CLOCK_RATE / SCREEN_REFRESH_RATE);
+constexpr s64 frame_ticks = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 60);
+constexpr s64 frame_ticks_30fps = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 30);
NVFlinger::NVFlinger(Core::Timing::CoreTiming& core_timing) : core_timing{core_timing} {
displays.emplace_back(0, "Default");
@@ -36,13 +37,15 @@ NVFlinger::NVFlinger(Core::Timing::CoreTiming& core_timing) : core_timing{core_t
displays.emplace_back(4, "Null");
// Schedule the screen composition events
- composition_event =
- core_timing.RegisterEvent("ScreenComposition", [this](u64 userdata, int cycles_late) {
+ const auto ticks = Settings::values.force_30fps_mode ? frame_ticks_30fps : frame_ticks;
+
+ composition_event = core_timing.RegisterEvent(
+ "ScreenComposition", [this, ticks](u64 userdata, s64 cycles_late) {
Compose();
- this->core_timing.ScheduleEvent(frame_ticks - cycles_late, composition_event);
+ this->core_timing.ScheduleEvent(ticks - cycles_late, composition_event);
});
- core_timing.ScheduleEvent(frame_ticks, composition_event);
+ core_timing.ScheduleEvent(ticks, composition_event);
}
NVFlinger::~NVFlinger() {
@@ -62,6 +65,7 @@ std::optional<u64> NVFlinger::OpenDisplay(std::string_view name) {
const auto itr =
std::find_if(displays.begin(), displays.end(),
[&](const VI::Display& display) { return display.GetName() == name; });
+
if (itr == displays.end()) {
return {};
}
diff --git a/src/core/hle/service/pctl/module.cpp b/src/core/hle/service/pctl/module.cpp
index 6081f41e1..c75b4ee34 100644
--- a/src/core/hle/service/pctl/module.cpp
+++ b/src/core/hle/service/pctl/module.cpp
@@ -12,10 +12,10 @@ namespace Service::PCTL {
class IParentalControlService final : public ServiceFramework<IParentalControlService> {
public:
IParentalControlService() : ServiceFramework("IParentalControlService") {
+ // clang-format off
static const FunctionInfo functions[] = {
{1, &IParentalControlService::Initialize, "Initialize"},
- {1001, &IParentalControlService::CheckFreeCommunicationPermission,
- "CheckFreeCommunicationPermission"},
+ {1001, &IParentalControlService::CheckFreeCommunicationPermission, "CheckFreeCommunicationPermission"},
{1002, nullptr, "ConfirmLaunchApplicationPermission"},
{1003, nullptr, "ConfirmResumeApplicationPermission"},
{1004, nullptr, "ConfirmSnsPostPermission"},
@@ -30,6 +30,7 @@ public:
{1013, nullptr, "ConfirmStereoVisionPermission"},
{1014, nullptr, "ConfirmPlayableApplicationVideoOld"},
{1015, nullptr, "ConfirmPlayableApplicationVideo"},
+ {1016, nullptr, "ConfirmShowNewsPermission"},
{1031, nullptr, "IsRestrictionEnabled"},
{1032, nullptr, "GetSafetyLevel"},
{1033, nullptr, "SetSafetyLevel"},
@@ -45,6 +46,7 @@ public:
{1045, nullptr, "UpdateFreeCommunicationApplicationList"},
{1046, nullptr, "DisableFeaturesForReset"},
{1047, nullptr, "NotifyApplicationDownloadStarted"},
+ {1048, nullptr, "NotifyNetworkProfileCreated"},
{1061, nullptr, "ConfirmStereoVisionRestrictionConfigurable"},
{1062, nullptr, "GetStereoVisionRestriction"},
{1063, nullptr, "SetStereoVisionRestriction"},
@@ -63,6 +65,7 @@ public:
{1411, nullptr, "GetPairingAccountInfo"},
{1421, nullptr, "GetAccountNickname"},
{1424, nullptr, "GetAccountState"},
+ {1425, nullptr, "RequestPostEvents"},
{1432, nullptr, "GetSynchronizationEvent"},
{1451, nullptr, "StartPlayTimer"},
{1452, nullptr, "StopPlayTimer"},
diff --git a/src/core/hle/service/pm/pm.cpp b/src/core/hle/service/pm/pm.cpp
index 6b27dc4a3..ebcc41a43 100644
--- a/src/core/hle/service/pm/pm.cpp
+++ b/src/core/hle/service/pm/pm.cpp
@@ -42,15 +42,18 @@ private:
class DebugMonitor final : public ServiceFramework<DebugMonitor> {
public:
explicit DebugMonitor() : ServiceFramework{"pm:dmnt"} {
+ // clang-format off
static const FunctionInfo functions[] = {
- {0, nullptr, "IsDebugMode"},
- {1, nullptr, "GetDebugProcesses"},
- {2, nullptr, "StartDebugProcess"},
- {3, nullptr, "GetTitlePid"},
- {4, nullptr, "EnableDebugForTitleId"},
- {5, nullptr, "GetApplicationPid"},
- {6, nullptr, "EnableDebugForApplication"},
+ {0, nullptr, "GetDebugProcesses"},
+ {1, nullptr, "StartDebugProcess"},
+ {2, nullptr, "GetTitlePid"},
+ {3, nullptr, "EnableDebugForTitleId"},
+ {4, nullptr, "GetApplicationPid"},
+ {5, nullptr, "EnableDebugForApplication"},
+ {6, nullptr, "DisableDebug"},
};
+ // clang-format on
+
RegisterHandlers(functions);
}
};
@@ -68,6 +71,7 @@ public:
class Shell final : public ServiceFramework<Shell> {
public:
explicit Shell() : ServiceFramework{"pm:shell"} {
+ // clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "LaunchProcess"},
{1, nullptr, "TerminateProcessByPid"},
@@ -77,7 +81,10 @@ public:
{5, nullptr, "NotifyBootFinished"},
{6, nullptr, "GetApplicationPid"},
{7, nullptr, "BoostSystemMemoryResourceLimit"},
+ {8, nullptr, "EnableAdditionalSystemThreads"},
};
+ // clang-format on
+
RegisterHandlers(functions);
}
};
diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp
index 117f87a45..6c69f899e 100644
--- a/src/core/hle/service/service.cpp
+++ b/src/core/hle/service/service.cpp
@@ -11,7 +11,6 @@
#include "core/hle/ipc.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/client_port.h"
-#include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/server_port.h"
@@ -76,7 +75,8 @@ namespace Service {
* Creates a function string for logging, complete with the name (or header code, depending
* on what's passed in) the port name, and all the cmd_buff arguments.
*/
-[[maybe_unused]] static std::string MakeFunctionString(const char* name, const char* port_name,
+[[maybe_unused]] static std::string MakeFunctionString(std::string_view name,
+ std::string_view port_name,
const u32* cmd_buff) {
// Number of params == bits 0-5 + bits 6-11
int num_params = (cmd_buff[0] & 0x3F) + ((cmd_buff[0] >> 6) & 0x3F);
@@ -158,9 +158,7 @@ void ServiceFrameworkBase::InvokeRequest(Kernel::HLERequestContext& ctx) {
return ReportUnimplementedFunction(ctx, info);
}
- LOG_TRACE(
- Service, "{}",
- MakeFunctionString(info->name, GetServiceName().c_str(), ctx.CommandBuffer()).c_str());
+ LOG_TRACE(Service, "{}", MakeFunctionString(info->name, GetServiceName(), ctx.CommandBuffer()));
handler_invoker(this, info->handler_callback, ctx);
}
@@ -169,7 +167,7 @@ ResultCode ServiceFrameworkBase::HandleSyncRequest(Kernel::HLERequestContext& co
case IPC::CommandType::Close: {
IPC::ResponseBuilder rb{context, 2};
rb.Push(RESULT_SUCCESS);
- return ResultCode(ErrorModule::HIPC, ErrorDescription::RemoteProcessDead);
+ return IPC::ERR_REMOTE_PROCESS_DEAD;
}
case IPC::CommandType::ControlWithContext:
case IPC::CommandType::Control: {
@@ -202,7 +200,7 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system,
SM::ServiceManager::InstallInterfaces(sm);
- Account::InstallInterfaces(*sm);
+ Account::InstallInterfaces(system);
AM::InstallInterfaces(*sm, nv_flinger);
AOC::InstallInterfaces(*sm);
APM::InstallInterfaces(*sm);
diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h
index 830790269..abbfe5524 100644
--- a/src/core/hle/service/service.h
+++ b/src/core/hle/service/service.h
@@ -90,7 +90,7 @@ private:
Kernel::HLERequestContext& ctx);
ServiceFrameworkBase(const char* service_name, u32 max_sessions, InvokerFn* handler_invoker);
- ~ServiceFrameworkBase();
+ ~ServiceFrameworkBase() override;
void RegisterHandlersBase(const FunctionInfoBase* functions, std::size_t n);
void ReportUnimplementedFunction(Kernel::HLERequestContext& ctx, const FunctionInfoBase* info);
diff --git a/src/core/hle/service/set/set.cpp b/src/core/hle/service/set/set.cpp
index 1afc43f75..298d85011 100644
--- a/src/core/hle/service/set/set.cpp
+++ b/src/core/hle/service/set/set.cpp
@@ -2,16 +2,15 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <algorithm>
#include <chrono>
#include "common/logging/log.h"
#include "core/hle/ipc_helpers.h"
-#include "core/hle/kernel/client_port.h"
-#include "core/hle/kernel/client_session.h"
#include "core/hle/service/set/set.h"
#include "core/settings.h"
namespace Service::Set {
-
+namespace {
constexpr std::array<LanguageCode, 17> available_language_codes = {{
LanguageCode::JA,
LanguageCode::EN_US,
@@ -32,41 +31,35 @@ constexpr std::array<LanguageCode, 17> available_language_codes = {{
LanguageCode::ZH_HANT,
}};
-constexpr std::size_t pre4_0_0_max_entries = 0xF;
-constexpr std::size_t post4_0_0_max_entries = 0x40;
+constexpr std::size_t pre4_0_0_max_entries = 15;
+constexpr std::size_t post4_0_0_max_entries = 17;
constexpr ResultCode ERR_INVALID_LANGUAGE{ErrorModule::Settings, 625};
-LanguageCode GetLanguageCodeFromIndex(std::size_t index) {
- return available_language_codes.at(index);
+void PushResponseLanguageCode(Kernel::HLERequestContext& ctx, std::size_t num_language_codes) {
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(static_cast<u32>(num_language_codes));
}
-template <std::size_t size>
-static std::array<LanguageCode, size> MakeLanguageCodeSubset() {
- std::array<LanguageCode, size> arr;
- std::copy_n(available_language_codes.begin(), size, arr.begin());
- return arr;
+void GetAvailableLanguageCodesImpl(Kernel::HLERequestContext& ctx, std::size_t max_size) {
+ const std::size_t requested_amount = ctx.GetWriteBufferSize() / sizeof(LanguageCode);
+ const std::size_t copy_amount = std::min(requested_amount, max_size);
+ const std::size_t copy_size = copy_amount * sizeof(LanguageCode);
+
+ ctx.WriteBuffer(available_language_codes.data(), copy_size);
+ PushResponseLanguageCode(ctx, copy_amount);
}
+} // Anonymous namespace
-static void PushResponseLanguageCode(Kernel::HLERequestContext& ctx, std::size_t max_size) {
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(RESULT_SUCCESS);
- if (available_language_codes.size() > max_size) {
- rb.Push(static_cast<u32>(max_size));
- } else {
- rb.Push(static_cast<u32>(available_language_codes.size()));
- }
+LanguageCode GetLanguageCodeFromIndex(std::size_t index) {
+ return available_language_codes.at(index);
}
void SET::GetAvailableLanguageCodes(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_SET, "called");
- if (available_language_codes.size() > pre4_0_0_max_entries) {
- ctx.WriteBuffer(MakeLanguageCodeSubset<pre4_0_0_max_entries>());
- } else {
- ctx.WriteBuffer(available_language_codes);
- }
- PushResponseLanguageCode(ctx, pre4_0_0_max_entries);
+ GetAvailableLanguageCodesImpl(ctx, pre4_0_0_max_entries);
}
void SET::MakeLanguageCode(Kernel::HLERequestContext& ctx) {
@@ -87,12 +80,7 @@ void SET::MakeLanguageCode(Kernel::HLERequestContext& ctx) {
void SET::GetAvailableLanguageCodes2(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_SET, "called");
- if (available_language_codes.size() > post4_0_0_max_entries) {
- ctx.WriteBuffer(MakeLanguageCodeSubset<post4_0_0_max_entries>());
- } else {
- ctx.WriteBuffer(available_language_codes);
- }
- PushResponseLanguageCode(ctx, post4_0_0_max_entries);
+ GetAvailableLanguageCodesImpl(ctx, post4_0_0_max_entries);
}
void SET::GetAvailableLanguageCodeCount(Kernel::HLERequestContext& ctx) {
@@ -102,9 +90,9 @@ void SET::GetAvailableLanguageCodeCount(Kernel::HLERequestContext& ctx) {
}
void SET::GetAvailableLanguageCodeCount2(Kernel::HLERequestContext& ctx) {
- PushResponseLanguageCode(ctx, post4_0_0_max_entries);
-
LOG_DEBUG(Service_SET, "called");
+
+ PushResponseLanguageCode(ctx, post4_0_0_max_entries);
}
void SET::GetLanguageCode(Kernel::HLERequestContext& ctx) {
@@ -116,6 +104,7 @@ void SET::GetLanguageCode(Kernel::HLERequestContext& ctx) {
}
SET::SET() : ServiceFramework("set") {
+ // clang-format off
static const FunctionInfo functions[] = {
{0, &SET::GetLanguageCode, "GetLanguageCode"},
{1, &SET::GetAvailableLanguageCodes, "GetAvailableLanguageCodes"},
@@ -126,7 +115,10 @@ SET::SET() : ServiceFramework("set") {
{6, &SET::GetAvailableLanguageCodeCount2, "GetAvailableLanguageCodeCount2"},
{7, nullptr, "GetKeyCodeMap"},
{8, nullptr, "GetQuestFlag"},
+ {9, nullptr, "GetKeyCodeMap2"},
};
+ // clang-format on
+
RegisterHandlers(functions);
}
diff --git a/src/core/hle/service/set/set_cal.cpp b/src/core/hle/service/set/set_cal.cpp
index 34654bb07..5981c575c 100644
--- a/src/core/hle/service/set/set_cal.cpp
+++ b/src/core/hle/service/set/set_cal.cpp
@@ -40,7 +40,7 @@ SET_CAL::SET_CAL() : ServiceFramework("set:cal") {
{30, nullptr, "GetAmiiboEcqvBlsCertificate"},
{31, nullptr, "GetAmiiboEcqvBlsRootCertificate"},
{32, nullptr, "GetUsbTypeCPowerSourceCircuitVersion"},
- {33, nullptr, "GetBatteryVersion"},
+ {41, nullptr, "GetBatteryVersion"},
};
RegisterHandlers(functions);
}
diff --git a/src/core/hle/service/set/set_cal.h b/src/core/hle/service/set/set_cal.h
index 583036eac..a0677e815 100644
--- a/src/core/hle/service/set/set_cal.h
+++ b/src/core/hle/service/set/set_cal.h
@@ -11,7 +11,7 @@ namespace Service::Set {
class SET_CAL final : public ServiceFramework<SET_CAL> {
public:
explicit SET_CAL();
- ~SET_CAL();
+ ~SET_CAL() override;
};
} // namespace Service::Set
diff --git a/src/core/hle/service/set/set_sys.cpp b/src/core/hle/service/set/set_sys.cpp
index c9b4da5b0..98d0cfdfd 100644
--- a/src/core/hle/service/set/set_sys.cpp
+++ b/src/core/hle/service/set/set_sys.cpp
@@ -2,13 +2,88 @@
// 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/file_sys/errors.h"
+#include "core/file_sys/system_archive/system_version.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/client_port.h"
+#include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/set/set_sys.h"
namespace Service::Set {
+namespace {
+constexpr u64 SYSTEM_VERSION_FILE_MINOR_REVISION_OFFSET = 0x05;
+
+enum class GetFirmwareVersionType {
+ Version1,
+ Version2,
+};
+
+void GetFirmwareVersionImpl(Kernel::HLERequestContext& ctx, GetFirmwareVersionType type) {
+ LOG_WARNING(Service_SET, "called - Using hardcoded firmware version '{}'",
+ FileSys::SystemArchive::GetLongDisplayVersion());
+
+ ASSERT_MSG(ctx.GetWriteBufferSize() == 0x100,
+ "FirmwareVersion output buffer must be 0x100 bytes in size!");
+
+ // Instead of using the normal procedure of checking for the real system archive and if it
+ // doesn't exist, synthesizing one, I feel that that would lead to strange bugs because a
+ // used is using a really old or really new SystemVersion title. The synthesized one ensures
+ // 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) {
+ LOG_ERROR(Service_SET, "General failure while attempting to resolve firmware version ({}).",
+ desc.c_str());
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(code);
+ };
+
+ if (archive == nullptr) {
+ early_exit_failure("The system version archive couldn't be synthesized.",
+ FileSys::ERROR_FAILED_MOUNT_ARCHIVE);
+ return;
+ }
+
+ const auto ver_file = archive->GetFile("file");
+ if (ver_file == nullptr) {
+ early_exit_failure("The system version archive didn't contain the file 'file'.",
+ FileSys::ERROR_INVALID_ARGUMENT);
+ return;
+ }
+
+ auto data = ver_file->ReadAllBytes();
+ if (data.size() != 0x100) {
+ early_exit_failure("The system version file 'file' was not the correct size.",
+ FileSys::ERROR_OUT_OF_BOUNDS);
+ return;
+ }
+
+ // If the command is GetFirmwareVersion (as opposed to GetFirmwareVersion2), hardware will
+ // zero out the REVISION_MINOR field.
+ if (type == GetFirmwareVersionType::Version1) {
+ data[SYSTEM_VERSION_FILE_MINOR_REVISION_OFFSET] = 0;
+ }
+
+ ctx.WriteBuffer(data);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+} // Anonymous namespace
+
+void SET_SYS::GetFirmwareVersion(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_SET, "called");
+ GetFirmwareVersionImpl(ctx, GetFirmwareVersionType::Version1);
+}
+
+void SET_SYS::GetFirmwareVersion2(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_SET, "called");
+ GetFirmwareVersionImpl(ctx, GetFirmwareVersionType::Version2);
+}
+
void SET_SYS::GetColorSetId(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_SET, "called");
@@ -29,12 +104,13 @@ void SET_SYS::SetColorSetId(Kernel::HLERequestContext& ctx) {
}
SET_SYS::SET_SYS() : ServiceFramework("set:sys") {
+ // clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "SetLanguageCode"},
{1, nullptr, "SetNetworkSettings"},
{2, nullptr, "GetNetworkSettings"},
- {3, nullptr, "GetFirmwareVersion"},
- {4, nullptr, "GetFirmwareVersion2"},
+ {3, &SET_SYS::GetFirmwareVersion, "GetFirmwareVersion"},
+ {4, &SET_SYS::GetFirmwareVersion2, "GetFirmwareVersion2"},
{5, nullptr, "GetFirmwareVersionDigest"},
{7, nullptr, "GetLockScreenFlag"},
{8, nullptr, "SetLockScreenFlag"},
@@ -177,7 +253,33 @@ SET_SYS::SET_SYS() : ServiceFramework("set:sys") {
{147, nullptr, "GetConsoleSixAxisSensorAngularAcceleration"},
{148, nullptr, "SetConsoleSixAxisSensorAngularAcceleration"},
{149, nullptr, "GetRebootlessSystemUpdateVersion"},
+ {150, nullptr, "GetDeviceTimeZoneLocationUpdatedTime"},
+ {151, nullptr, "SetDeviceTimeZoneLocationUpdatedTime"},
+ {152, nullptr, "GetUserSystemClockAutomaticCorrectionUpdatedTime"},
+ {153, nullptr, "SetUserSystemClockAutomaticCorrectionUpdatedTime"},
+ {154, nullptr, "GetAccountOnlineStorageSettings"},
+ {155, nullptr, "SetAccountOnlineStorageSettings"},
+ {156, nullptr, "GetPctlReadyFlag"},
+ {157, nullptr, "SetPctlReadyFlag"},
+ {162, nullptr, "GetPtmBatteryVersion"},
+ {163, nullptr, "SetPtmBatteryVersion"},
+ {164, nullptr, "GetUsb30HostEnableFlag"},
+ {165, nullptr, "SetUsb30HostEnableFlag"},
+ {166, nullptr, "GetUsb30DeviceEnableFlag"},
+ {167, nullptr, "SetUsb30DeviceEnableFlag"},
+ {168, nullptr, "GetThemeId"},
+ {169, nullptr, "SetThemeId"},
+ {170, nullptr, "GetChineseTraditionalInputMethod"},
+ {171, nullptr, "SetChineseTraditionalInputMethod"},
+ {172, nullptr, "GetPtmCycleCountReliability"},
+ {173, nullptr, "SetPtmCycleCountReliability"},
+ {175, nullptr, "GetThemeSettings"},
+ {176, nullptr, "SetThemeSettings"},
+ {177, nullptr, "GetThemeKey"},
+ {178, nullptr, "SetThemeKey"},
};
+ // clang-format on
+
RegisterHandlers(functions);
}
diff --git a/src/core/hle/service/set/set_sys.h b/src/core/hle/service/set/set_sys.h
index f602f3c77..13ee2cf46 100644
--- a/src/core/hle/service/set/set_sys.h
+++ b/src/core/hle/service/set/set_sys.h
@@ -20,6 +20,8 @@ private:
BasicBlack = 1,
};
+ void GetFirmwareVersion(Kernel::HLERequestContext& ctx);
+ void GetFirmwareVersion2(Kernel::HLERequestContext& ctx);
void GetColorSetId(Kernel::HLERequestContext& ctx);
void SetColorSetId(Kernel::HLERequestContext& ctx);
diff --git a/src/core/hle/service/sm/controller.cpp b/src/core/hle/service/sm/controller.cpp
index 74da4d5e6..e9ee73710 100644
--- a/src/core/hle/service/sm/controller.cpp
+++ b/src/core/hle/service/sm/controller.cpp
@@ -30,7 +30,7 @@ void Controller::DuplicateSession(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1, IPC::ResponseBuilder::Flags::AlwaysMoveHandles};
rb.Push(RESULT_SUCCESS);
- Kernel::SharedPtr<Kernel::ClientSession> session{ctx.Session()->parent->client};
+ Kernel::SharedPtr<Kernel::ClientSession> session{ctx.Session()->GetParent()->client};
rb.PushMoveObjects(session);
LOG_DEBUG(Service, "session={}", session->GetObjectId());
diff --git a/src/core/hle/service/sm/sm.h b/src/core/hle/service/sm/sm.h
index bef25433e..b9d6381b4 100644
--- a/src/core/hle/service/sm/sm.h
+++ b/src/core/hle/service/sm/sm.h
@@ -67,7 +67,7 @@ public:
if (port == nullptr) {
return nullptr;
}
- return std::static_pointer_cast<T>(port->hle_handler);
+ return std::static_pointer_cast<T>(port->GetHLEHandler());
}
void InvokeControlRequest(Kernel::HLERequestContext& context);
diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp
index 4342f3b2d..884ad173b 100644
--- a/src/core/hle/service/sockets/bsd.cpp
+++ b/src/core/hle/service/sockets/bsd.cpp
@@ -73,6 +73,7 @@ void BSD::Close(Kernel::HLERequestContext& ctx) {
}
BSD::BSD(const char* name) : ServiceFramework(name) {
+ // clang-format off
static const FunctionInfo functions[] = {
{0, &BSD::RegisterClient, "RegisterClient"},
{1, &BSD::StartMonitoring, "StartMonitoring"},
@@ -105,7 +106,11 @@ BSD::BSD(const char* name) : ServiceFramework(name) {
{28, nullptr, "GetResourceStatistics"},
{29, nullptr, "RecvMMsg"},
{30, nullptr, "SendMMsg"},
+ {31, nullptr, "EventFd"},
+ {32, nullptr, "RegisterResourceStatisticsName"},
};
+ // clang-format on
+
RegisterHandlers(functions);
}
diff --git a/src/core/hle/service/sockets/sfdnsres.cpp b/src/core/hle/service/sockets/sfdnsres.cpp
index 13ab1d31e..852e71e4b 100644
--- a/src/core/hle/service/sockets/sfdnsres.cpp
+++ b/src/core/hle/service/sockets/sfdnsres.cpp
@@ -8,12 +8,20 @@
namespace Service::Sockets {
void SFDNSRES::GetAddrInfo(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");
+ 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);
}
diff --git a/src/core/hle/service/spl/module.cpp b/src/core/hle/service/spl/module.cpp
index 8db0c2f13..e724d4ab8 100644
--- a/src/core/hle/service/spl/module.cpp
+++ b/src/core/hle/service/spl/module.cpp
@@ -26,9 +26,7 @@ Module::Interface::~Interface() = default;
void Module::Interface::GetRandomBytes(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_SPL, "called");
- IPC::RequestParser rp{ctx};
-
- std::size_t size = ctx.GetWriteBufferSize();
+ const std::size_t size = ctx.GetWriteBufferSize();
std::uniform_int_distribution<u16> distribution(0, std::numeric_limits<u8>::max());
std::vector<u8> data(size);
diff --git a/src/core/hle/service/ssl/ssl.cpp b/src/core/hle/service/ssl/ssl.cpp
index af40a1815..65040c077 100644
--- a/src/core/hle/service/ssl/ssl.cpp
+++ b/src/core/hle/service/ssl/ssl.cpp
@@ -64,13 +64,19 @@ public:
};
RegisterHandlers(functions);
}
- ~ISslContext() = default;
private:
void SetOption(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_SSL, "(STUBBED) called");
+ struct Parameters {
+ u8 enable;
+ u32 option;
+ };
IPC::RequestParser rp{ctx};
+ const auto parameters = rp.PopRaw<Parameters>();
+
+ LOG_WARNING(Service_SSL, "(STUBBED) called. enable={}, option={}", parameters.enable,
+ parameters.option);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -97,6 +103,8 @@ public:
{4, nullptr, "DebugIoctl"},
{5, &SSL::SetInterfaceVersion, "SetInterfaceVersion"},
{6, nullptr, "FlushSessionCache"},
+ {7, nullptr, "SetDebugOption"},
+ {8, nullptr, "GetDebugOption"},
};
// clang-format on
diff --git a/src/core/hle/service/time/interface.cpp b/src/core/hle/service/time/interface.cpp
index b3a196f65..8d122ae33 100644
--- a/src/core/hle/service/time/interface.cpp
+++ b/src/core/hle/service/time/interface.cpp
@@ -8,6 +8,7 @@ namespace Service::Time {
Time::Time(std::shared_ptr<Module> time, const char* name)
: Module::Interface(std::move(time), name) {
+ // clang-format off
static const FunctionInfo functions[] = {
{0, &Time::GetStandardUserSystemClock, "GetStandardUserSystemClock"},
{1, &Time::GetStandardNetworkSystemClock, "GetStandardNetworkSystemClock"},
@@ -15,18 +16,23 @@ Time::Time(std::shared_ptr<Module> time, const char* name)
{3, &Time::GetTimeZoneService, "GetTimeZoneService"},
{4, &Time::GetStandardLocalSystemClock, "GetStandardLocalSystemClock"},
{5, nullptr, "GetEphemeralNetworkSystemClock"},
+ {20, nullptr, "GetSharedMemoryNativeHandle"},
+ {30, nullptr, "GetStandardNetworkClockOperationEventReadableHandle"},
+ {31, nullptr, "GetEphemeralNetworkClockOperationEventReadableHandle"},
{50, nullptr, "SetStandardSteadyClockInternalOffset"},
{100, nullptr, "IsStandardUserSystemClockAutomaticCorrectionEnabled"},
{101, nullptr, "SetStandardUserSystemClockAutomaticCorrectionEnabled"},
{102, nullptr, "GetStandardUserSystemClockInitialYear"},
{200, nullptr, "IsStandardNetworkSystemClockAccuracySufficient"},
+ {201, nullptr, "GetStandardUserSystemClockAutomaticCorrectionUpdatedTime"},
{300, nullptr, "CalculateMonotonicSystemClockBaseTimePoint"},
{400, &Time::GetClockSnapshot, "GetClockSnapshot"},
{401, nullptr, "GetClockSnapshotFromSystemClockContext"},
- {500, &Time::CalculateStandardUserSystemClockDifferenceByUser,
- "CalculateStandardUserSystemClockDifferenceByUser"},
+ {500, &Time::CalculateStandardUserSystemClockDifferenceByUser, "CalculateStandardUserSystemClockDifferenceByUser"},
{501, nullptr, "CalculateSpanBetween"},
};
+ // clang-format on
+
RegisterHandlers(functions);
}
diff --git a/src/core/hle/service/time/time.cpp b/src/core/hle/service/time/time.cpp
index aa115935d..346bad80d 100644
--- a/src/core/hle/service/time/time.cpp
+++ b/src/core/hle/service/time/time.cpp
@@ -108,8 +108,9 @@ private:
LOG_DEBUG(Service_Time, "called");
const auto& core_timing = Core::System::GetInstance().CoreTiming();
- const SteadyClockTimePoint steady_clock_time_point{
- Core::Timing::cyclesToMs(core_timing.GetTicks()) / 1000};
+ const auto ms = Core::Timing::CyclesToMs(core_timing.GetTicks());
+ const SteadyClockTimePoint steady_clock_time_point{static_cast<u64_le>(ms.count() / 1000),
+ {}};
IPC::ResponseBuilder rb{ctx, (sizeof(SteadyClockTimePoint) / 4) + 2};
rb.Push(RESULT_SUCCESS);
rb.PushRaw(steady_clock_time_point);
@@ -284,8 +285,8 @@ void Module::Interface::GetClockSnapshot(Kernel::HLERequestContext& ctx) {
}
const auto& core_timing = Core::System::GetInstance().CoreTiming();
- const SteadyClockTimePoint steady_clock_time_point{
- Core::Timing::cyclesToMs(core_timing.GetTicks()) / 1000, {}};
+ const auto ms = Core::Timing::CyclesToMs(core_timing.GetTicks());
+ const SteadyClockTimePoint steady_clock_time_point{static_cast<u64_le>(ms.count() / 1000), {}};
CalendarTime calendar_time{};
calendar_time.year = tm->tm_year + 1900;
diff --git a/src/core/hle/service/vi/display/vi_display.cpp b/src/core/hle/service/vi/display/vi_display.cpp
index 01d80311b..a8d088305 100644
--- a/src/core/hle/service/vi/display/vi_display.cpp
+++ b/src/core/hle/service/vi/display/vi_display.cpp
@@ -17,7 +17,7 @@ namespace Service::VI {
Display::Display(u64 id, std::string name) : id{id}, name{std::move(name)} {
auto& kernel = Core::System::GetInstance().Kernel();
- vsync_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Sticky,
+ vsync_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Manual,
fmt::format("Display VSync Event {}", id));
}
diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp
index a975767bb..f1fa6ccd1 100644
--- a/src/core/hle/service/vi/vi.cpp
+++ b/src/core/hle/service/vi/vi.cpp
@@ -24,6 +24,7 @@
#include "core/hle/service/nvdrv/nvdrv.h"
#include "core/hle/service/nvflinger/buffer_queue.h"
#include "core/hle/service/nvflinger/nvflinger.h"
+#include "core/hle/service/service.h"
#include "core/hle/service/vi/vi.h"
#include "core/hle/service/vi/vi_m.h"
#include "core/hle/service/vi/vi_s.h"
@@ -33,6 +34,7 @@
namespace Service::VI {
constexpr ResultCode ERR_OPERATION_FAILED{ErrorModule::VI, 1};
+constexpr ResultCode ERR_PERMISSION_DENIED{ErrorModule::VI, 5};
constexpr ResultCode ERR_UNSUPPORTED{ErrorModule::VI, 6};
constexpr ResultCode ERR_NOT_FOUND{ErrorModule::VI, 7};
@@ -496,7 +498,6 @@ public:
};
RegisterHandlers(functions);
}
- ~IHOSBinderDriver() = default;
private:
enum class TransactionId {
@@ -555,7 +556,7 @@ private:
} else {
// Wait the current thread until a buffer becomes available
ctx.SleepClientThread(
- Kernel::GetCurrentThread(), "IHOSBinderDriver::DequeueBuffer", -1,
+ "IHOSBinderDriver::DequeueBuffer", -1,
[=](Kernel::SharedPtr<Kernel::Thread> thread, Kernel::HLERequestContext& ctx,
Kernel::ThreadWakeupReason reason) {
// Repeat TransactParcel DequeueBuffer when a buffer is available
@@ -690,7 +691,6 @@ public:
};
RegisterHandlers(functions);
}
- ~ISystemDisplayService() = default;
private:
void SetLayerZ(Kernel::HLERequestContext& ctx) {
@@ -816,7 +816,6 @@ public:
};
RegisterHandlers(functions);
}
- ~IManagerDisplayService() = default;
private:
void CloseDisplay(Kernel::HLERequestContext& ctx) {
@@ -882,7 +881,6 @@ private:
class IApplicationDisplayService final : public ServiceFramework<IApplicationDisplayService> {
public:
explicit IApplicationDisplayService(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger);
- ~IApplicationDisplayService() = default;
private:
enum class ConvertedScaleMode : u64 {
@@ -1035,7 +1033,6 @@ private:
void ListDisplays(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_VI, "(STUBBED) called");
- IPC::RequestParser rp{ctx};
DisplayInfo display_info;
display_info.width *= static_cast<u64>(Settings::values.resolution_factor);
display_info.height *= static_cast<u64>(Settings::values.resolution_factor);
@@ -1203,26 +1200,40 @@ IApplicationDisplayService::IApplicationDisplayService(
RegisterHandlers(functions);
}
-Module::Interface::Interface(std::shared_ptr<Module> module, const char* name,
- std::shared_ptr<NVFlinger::NVFlinger> nv_flinger)
- : ServiceFramework(name), module(std::move(module)), nv_flinger(std::move(nv_flinger)) {}
+static bool IsValidServiceAccess(Permission permission, Policy policy) {
+ if (permission == Permission::User) {
+ return policy == Policy::User;
+ }
+
+ if (permission == Permission::System || permission == Permission::Manager) {
+ return policy == Policy::User || policy == Policy::Compositor;
+ }
+
+ return false;
+}
-Module::Interface::~Interface() = default;
+void detail::GetDisplayServiceImpl(Kernel::HLERequestContext& ctx,
+ std::shared_ptr<NVFlinger::NVFlinger> nv_flinger,
+ Permission permission) {
+ IPC::RequestParser rp{ctx};
+ const auto policy = rp.PopEnum<Policy>();
-void Module::Interface::GetDisplayService(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_VI, "(STUBBED) called");
+ if (!IsValidServiceAccess(permission, policy)) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERR_PERMISSION_DENIED);
+ return;
+ }
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IApplicationDisplayService>(nv_flinger);
+ rb.PushIpcInterface<IApplicationDisplayService>(std::move(nv_flinger));
}
void InstallInterfaces(SM::ServiceManager& service_manager,
std::shared_ptr<NVFlinger::NVFlinger> nv_flinger) {
- auto module = std::make_shared<Module>();
- std::make_shared<VI_M>(module, nv_flinger)->InstallAsService(service_manager);
- std::make_shared<VI_S>(module, nv_flinger)->InstallAsService(service_manager);
- std::make_shared<VI_U>(module, nv_flinger)->InstallAsService(service_manager);
+ 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);
}
} // namespace Service::VI
diff --git a/src/core/hle/service/vi/vi.h b/src/core/hle/service/vi/vi.h
index e3963502a..6b66f8b81 100644
--- a/src/core/hle/service/vi/vi.h
+++ b/src/core/hle/service/vi/vi.h
@@ -4,12 +4,21 @@
#pragma once
-#include "core/hle/service/service.h"
+#include <memory>
+#include "common/common_types.h"
+
+namespace Kernel {
+class HLERequestContext;
+}
namespace Service::NVFlinger {
class NVFlinger;
}
+namespace Service::SM {
+class ServiceManager;
+}
+
namespace Service::VI {
enum class DisplayResolution : u32 {
@@ -19,22 +28,25 @@ enum class DisplayResolution : u32 {
UndockedHeight = 720,
};
-class Module final {
-public:
- class Interface : public ServiceFramework<Interface> {
- public:
- explicit Interface(std::shared_ptr<Module> module, const char* name,
- std::shared_ptr<NVFlinger::NVFlinger> nv_flinger);
- ~Interface() override;
-
- void GetDisplayService(Kernel::HLERequestContext& ctx);
+/// Permission level for a particular VI service instance
+enum class Permission {
+ User,
+ System,
+ Manager,
+};
- protected:
- std::shared_ptr<Module> module;
- std::shared_ptr<NVFlinger::NVFlinger> nv_flinger;
- };
+/// A policy type that may be requested via GetDisplayService and
+/// GetDisplayServiceWithProxyNameExchange
+enum class Policy {
+ User,
+ Compositor,
};
+namespace detail {
+void GetDisplayServiceImpl(Kernel::HLERequestContext& ctx,
+ std::shared_ptr<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);
diff --git a/src/core/hle/service/vi/vi_m.cpp b/src/core/hle/service/vi/vi_m.cpp
index 207c06b16..06070087f 100644
--- a/src/core/hle/service/vi/vi_m.cpp
+++ b/src/core/hle/service/vi/vi_m.cpp
@@ -2,12 +2,14 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include "common/logging/log.h"
+#include "core/hle/service/vi/vi.h"
#include "core/hle/service/vi/vi_m.h"
namespace Service::VI {
-VI_M::VI_M(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger)
- : Module::Interface(std::move(module), "vi:m", std::move(nv_flinger)) {
+VI_M::VI_M(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger)
+ : ServiceFramework{"vi:m"}, nv_flinger{std::move(nv_flinger)} {
static const FunctionInfo functions[] = {
{2, &VI_M::GetDisplayService, "GetDisplayService"},
{3, nullptr, "GetDisplayServiceWithProxyNameExchange"},
@@ -17,4 +19,10 @@ VI_M::VI_M(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger>
VI_M::~VI_M() = default;
+void VI_M::GetDisplayService(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_VI, "called");
+
+ detail::GetDisplayServiceImpl(ctx, 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 487d58d50..290e06689 100644
--- a/src/core/hle/service/vi/vi_m.h
+++ b/src/core/hle/service/vi/vi_m.h
@@ -4,14 +4,27 @@
#pragma once
-#include "core/hle/service/vi/vi.h"
+#include "core/hle/service/service.h"
+
+namespace Kernel {
+class HLERequestContext;
+}
+
+namespace Service::NVFlinger {
+class NVFlinger;
+}
namespace Service::VI {
-class VI_M final : public Module::Interface {
+class VI_M final : public ServiceFramework<VI_M> {
public:
- explicit VI_M(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger);
+ explicit VI_M(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger);
~VI_M() override;
+
+private:
+ void GetDisplayService(Kernel::HLERequestContext& ctx);
+
+ std::shared_ptr<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 920e6a1f6..57c596cc4 100644
--- a/src/core/hle/service/vi/vi_s.cpp
+++ b/src/core/hle/service/vi/vi_s.cpp
@@ -2,12 +2,14 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include "common/logging/log.h"
+#include "core/hle/service/vi/vi.h"
#include "core/hle/service/vi/vi_s.h"
namespace Service::VI {
-VI_S::VI_S(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger)
- : Module::Interface(std::move(module), "vi:s", std::move(nv_flinger)) {
+VI_S::VI_S(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger)
+ : ServiceFramework{"vi:s"}, nv_flinger{std::move(nv_flinger)} {
static const FunctionInfo functions[] = {
{1, &VI_S::GetDisplayService, "GetDisplayService"},
{3, nullptr, "GetDisplayServiceWithProxyNameExchange"},
@@ -17,4 +19,10 @@ VI_S::VI_S(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger>
VI_S::~VI_S() = default;
+void VI_S::GetDisplayService(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_VI, "called");
+
+ detail::GetDisplayServiceImpl(ctx, 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 bbc31148f..47804dc0b 100644
--- a/src/core/hle/service/vi/vi_s.h
+++ b/src/core/hle/service/vi/vi_s.h
@@ -4,14 +4,27 @@
#pragma once
-#include "core/hle/service/vi/vi.h"
+#include "core/hle/service/service.h"
+
+namespace Kernel {
+class HLERequestContext;
+}
+
+namespace Service::NVFlinger {
+class NVFlinger;
+}
namespace Service::VI {
-class VI_S final : public Module::Interface {
+class VI_S final : public ServiceFramework<VI_S> {
public:
- explicit VI_S(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger);
+ explicit VI_S(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger);
~VI_S() override;
+
+private:
+ void GetDisplayService(Kernel::HLERequestContext& ctx);
+
+ std::shared_ptr<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 d81e410d6..9d5ceb608 100644
--- a/src/core/hle/service/vi/vi_u.cpp
+++ b/src/core/hle/service/vi/vi_u.cpp
@@ -2,12 +2,14 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include "common/logging/log.h"
+#include "core/hle/service/vi/vi.h"
#include "core/hle/service/vi/vi_u.h"
namespace Service::VI {
-VI_U::VI_U(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger)
- : Module::Interface(std::move(module), "vi:u", std::move(nv_flinger)) {
+VI_U::VI_U(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger)
+ : ServiceFramework{"vi:u"}, nv_flinger{std::move(nv_flinger)} {
static const FunctionInfo functions[] = {
{0, &VI_U::GetDisplayService, "GetDisplayService"},
};
@@ -16,4 +18,10 @@ VI_U::VI_U(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger>
VI_U::~VI_U() = default;
+void VI_U::GetDisplayService(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_VI, "called");
+
+ detail::GetDisplayServiceImpl(ctx, 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 b92f28c92..19bdb73b0 100644
--- a/src/core/hle/service/vi/vi_u.h
+++ b/src/core/hle/service/vi/vi_u.h
@@ -4,14 +4,27 @@
#pragma once
-#include "core/hle/service/vi/vi.h"
+#include "core/hle/service/service.h"
+
+namespace Kernel {
+class HLERequestContext;
+}
+
+namespace Service::NVFlinger {
+class NVFlinger;
+}
namespace Service::VI {
-class VI_U final : public Module::Interface {
+class VI_U final : public ServiceFramework<VI_U> {
public:
- explicit VI_U(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger);
+ explicit VI_U(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger);
~VI_U() override;
+
+private:
+ void GetDisplayService(Kernel::HLERequestContext& ctx);
+
+ std::shared_ptr<NVFlinger::NVFlinger> nv_flinger;
};
} // namespace Service::VI
diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp
index 07aa7a1cd..10b13fb1d 100644
--- a/src/core/loader/deconstructed_rom_directory.cpp
+++ b/src/core/loader/deconstructed_rom_directory.cpp
@@ -86,25 +86,29 @@ FileType AppLoader_DeconstructedRomDirectory::IdentifyType(const FileSys::Virtua
return FileType::Error;
}
-ResultStatus AppLoader_DeconstructedRomDirectory::Load(Kernel::Process& process) {
+AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirectory::Load(
+ Kernel::Process& process) {
if (is_loaded) {
- return ResultStatus::ErrorAlreadyLoaded;
+ return {ResultStatus::ErrorAlreadyLoaded, {}};
}
if (dir == nullptr) {
- if (file == nullptr)
- return ResultStatus::ErrorNullFile;
+ if (file == nullptr) {
+ return {ResultStatus::ErrorNullFile, {}};
+ }
+
dir = file->GetContainingDirectory();
}
// Read meta to determine title ID
FileSys::VirtualFile npdm = dir->GetFile("main.npdm");
- if (npdm == nullptr)
- return ResultStatus::ErrorMissingNPDM;
+ if (npdm == nullptr) {
+ return {ResultStatus::ErrorMissingNPDM, {}};
+ }
- ResultStatus result = metadata.Load(npdm);
+ const ResultStatus result = metadata.Load(npdm);
if (result != ResultStatus::Success) {
- return result;
+ return {result, {}};
}
if (override_update) {
@@ -114,23 +118,24 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load(Kernel::Process& process)
// Reread in case PatchExeFS affected the main.npdm
npdm = dir->GetFile("main.npdm");
- if (npdm == nullptr)
- return ResultStatus::ErrorMissingNPDM;
+ if (npdm == nullptr) {
+ return {ResultStatus::ErrorMissingNPDM, {}};
+ }
- ResultStatus result2 = metadata.Load(npdm);
+ const ResultStatus result2 = metadata.Load(npdm);
if (result2 != ResultStatus::Success) {
- return result2;
+ return {result2, {}};
}
metadata.Print();
const FileSys::ProgramAddressSpaceType arch_bits{metadata.GetAddressSpaceType()};
if (arch_bits == FileSys::ProgramAddressSpaceType::Is32Bit ||
arch_bits == FileSys::ProgramAddressSpaceType::Is32BitNoMap) {
- return ResultStatus::Error32BitISA;
+ return {ResultStatus::Error32BitISA, {}};
}
if (process.LoadFromMetadata(metadata).IsError()) {
- return ResultStatus::ErrorUnableToParseKernelMetadata;
+ return {ResultStatus::ErrorUnableToParseKernelMetadata, {}};
}
const FileSys::PatchManager pm(metadata.GetTitleID());
@@ -150,7 +155,7 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load(Kernel::Process& process)
const auto tentative_next_load_addr =
AppLoader_NSO::LoadModule(process, *module_file, load_addr, should_pass_arguments, pm);
if (!tentative_next_load_addr) {
- return ResultStatus::ErrorLoadingNSO;
+ return {ResultStatus::ErrorLoadingNSO, {}};
}
next_load_addr = *tentative_next_load_addr;
@@ -159,8 +164,6 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load(Kernel::Process& process)
GDBStub::RegisterModule(module, load_addr, next_load_addr - 1, false);
}
- process.Run(base_address, metadata.GetMainThreadPriority(), metadata.GetMainThreadStackSize());
-
// Find the RomFS by searching for a ".romfs" file in this directory
const auto& files = dir->GetFiles();
const auto romfs_iter =
@@ -175,7 +178,8 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load(Kernel::Process& process)
}
is_loaded = true;
- return ResultStatus::Success;
+ return {ResultStatus::Success,
+ LoadParameters{metadata.GetMainThreadPriority(), metadata.GetMainThreadStackSize()}};
}
ResultStatus AppLoader_DeconstructedRomDirectory::ReadRomFS(FileSys::VirtualFile& dir) {
diff --git a/src/core/loader/deconstructed_rom_directory.h b/src/core/loader/deconstructed_rom_directory.h
index 1615cb5a8..1a65c16a4 100644
--- a/src/core/loader/deconstructed_rom_directory.h
+++ b/src/core/loader/deconstructed_rom_directory.h
@@ -37,7 +37,7 @@ public:
return IdentifyType(file);
}
- ResultStatus Load(Kernel::Process& process) override;
+ LoadResult Load(Kernel::Process& process) 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 6057c7f26..6d4b02375 100644
--- a/src/core/loader/elf.cpp
+++ b/src/core/loader/elf.cpp
@@ -9,6 +9,7 @@
#include "common/common_types.h"
#include "common/file_util.h"
#include "common/logging/log.h"
+#include "core/hle/kernel/code_set.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/vm_manager.h"
#include "core/loader/elf.h"
@@ -340,7 +341,7 @@ Kernel::CodeSet ElfReader::LoadInto(VAddr vaddr) {
}
codeset.entrypoint = base_addr + header->e_entry;
- codeset.memory = std::make_shared<std::vector<u8>>(std::move(program_image));
+ codeset.memory = std::move(program_image);
LOG_DEBUG(Loader, "Done loading.");
@@ -381,13 +382,15 @@ FileType AppLoader_ELF::IdentifyType(const FileSys::VirtualFile& file) {
return FileType::Error;
}
-ResultStatus AppLoader_ELF::Load(Kernel::Process& process) {
- if (is_loaded)
- return ResultStatus::ErrorAlreadyLoaded;
+AppLoader_ELF::LoadResult AppLoader_ELF::Load(Kernel::Process& process) {
+ if (is_loaded) {
+ return {ResultStatus::ErrorAlreadyLoaded, {}};
+ }
std::vector<u8> buffer = file->ReadAllBytes();
- if (buffer.size() != file->GetSize())
- return ResultStatus::ErrorIncorrectELFFileSize;
+ if (buffer.size() != file->GetSize()) {
+ return {ResultStatus::ErrorIncorrectELFFileSize, {}};
+ }
const VAddr base_address = process.VMManager().GetCodeRegionBaseAddress();
ElfReader elf_reader(&buffer[0]);
@@ -395,10 +398,9 @@ ResultStatus AppLoader_ELF::Load(Kernel::Process& process) {
const VAddr entry_point = codeset.entrypoint;
process.LoadModule(std::move(codeset), entry_point);
- process.Run(entry_point, 48, Memory::DEFAULT_STACK_SIZE);
is_loaded = true;
- return ResultStatus::Success;
+ return {ResultStatus::Success, LoadParameters{48, Memory::DEFAULT_STACK_SIZE}};
}
} // namespace Loader
diff --git a/src/core/loader/elf.h b/src/core/loader/elf.h
index a2d33021c..7ef7770a6 100644
--- a/src/core/loader/elf.h
+++ b/src/core/loader/elf.h
@@ -26,7 +26,7 @@ public:
return IdentifyType(file);
}
- ResultStatus Load(Kernel::Process& process) override;
+ LoadResult Load(Kernel::Process& process) override;
};
} // namespace Loader
diff --git a/src/core/loader/linker.cpp b/src/core/loader/linker.cpp
deleted file mode 100644
index 57ca8c3ee..000000000
--- a/src/core/loader/linker.cpp
+++ /dev/null
@@ -1,147 +0,0 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <vector>
-
-#include "common/common_funcs.h"
-#include "common/logging/log.h"
-#include "common/swap.h"
-#include "core/loader/linker.h"
-#include "core/memory.h"
-
-namespace Loader {
-
-enum class RelocationType : u32 { ABS64 = 257, GLOB_DAT = 1025, JUMP_SLOT = 1026, RELATIVE = 1027 };
-
-enum DynamicType : u32 {
- DT_NULL = 0,
- DT_PLTRELSZ = 2,
- DT_STRTAB = 5,
- DT_SYMTAB = 6,
- DT_RELA = 7,
- DT_RELASZ = 8,
- DT_STRSZ = 10,
- DT_JMPREL = 23,
-};
-
-struct Elf64_Rela {
- u64_le offset;
- RelocationType type;
- u32_le symbol;
- s64_le addend;
-};
-static_assert(sizeof(Elf64_Rela) == 0x18, "Elf64_Rela has incorrect size.");
-
-struct Elf64_Dyn {
- u64_le tag;
- u64_le value;
-};
-static_assert(sizeof(Elf64_Dyn) == 0x10, "Elf64_Dyn has incorrect size.");
-
-struct Elf64_Sym {
- u32_le name;
- INSERT_PADDING_BYTES(0x2);
- u16_le shndx;
- u64_le value;
- u64_le size;
-};
-static_assert(sizeof(Elf64_Sym) == 0x18, "Elf64_Sym has incorrect size.");
-
-void Linker::WriteRelocations(std::vector<u8>& program_image, const std::vector<Symbol>& symbols,
- u64 relocation_offset, u64 size, VAddr load_base) {
- for (u64 i = 0; i < size; i += sizeof(Elf64_Rela)) {
- Elf64_Rela rela;
- std::memcpy(&rela, &program_image[relocation_offset + i], sizeof(Elf64_Rela));
-
- const Symbol& symbol = symbols[rela.symbol];
- switch (rela.type) {
- case RelocationType::RELATIVE: {
- const u64 value = load_base + rela.addend;
- if (!symbol.name.empty()) {
- exports[symbol.name] = value;
- }
- std::memcpy(&program_image[rela.offset], &value, sizeof(u64));
- break;
- }
- case RelocationType::JUMP_SLOT:
- case RelocationType::GLOB_DAT:
- if (!symbol.value) {
- imports[symbol.name] = {rela.offset + load_base, 0};
- } else {
- exports[symbol.name] = symbol.value;
- std::memcpy(&program_image[rela.offset], &symbol.value, sizeof(u64));
- }
- break;
- case RelocationType::ABS64:
- if (!symbol.value) {
- imports[symbol.name] = {rela.offset + load_base, rela.addend};
- } else {
- const u64 value = symbol.value + rela.addend;
- exports[symbol.name] = value;
- std::memcpy(&program_image[rela.offset], &value, sizeof(u64));
- }
- break;
- default:
- LOG_CRITICAL(Loader, "Unknown relocation type: {}", static_cast<int>(rela.type));
- break;
- }
- }
-}
-
-void Linker::Relocate(std::vector<u8>& program_image, u32 dynamic_section_offset, VAddr load_base) {
- std::map<u64, u64> dynamic;
- while (dynamic_section_offset < program_image.size()) {
- Elf64_Dyn dyn;
- std::memcpy(&dyn, &program_image[dynamic_section_offset], sizeof(Elf64_Dyn));
- dynamic_section_offset += sizeof(Elf64_Dyn);
-
- if (dyn.tag == DT_NULL) {
- break;
- }
- dynamic[dyn.tag] = dyn.value;
- }
-
- u64 offset = dynamic[DT_SYMTAB];
- std::vector<Symbol> symbols;
- while (offset < program_image.size()) {
- Elf64_Sym sym;
- std::memcpy(&sym, &program_image[offset], sizeof(Elf64_Sym));
- offset += sizeof(Elf64_Sym);
-
- if (sym.name >= dynamic[DT_STRSZ]) {
- break;
- }
-
- std::string name = reinterpret_cast<char*>(&program_image[dynamic[DT_STRTAB] + sym.name]);
- if (sym.value) {
- exports[name] = load_base + sym.value;
- symbols.emplace_back(std::move(name), load_base + sym.value);
- } else {
- symbols.emplace_back(std::move(name), 0);
- }
- }
-
- if (dynamic.find(DT_RELA) != dynamic.end()) {
- WriteRelocations(program_image, symbols, dynamic[DT_RELA], dynamic[DT_RELASZ], load_base);
- }
-
- if (dynamic.find(DT_JMPREL) != dynamic.end()) {
- WriteRelocations(program_image, symbols, dynamic[DT_JMPREL], dynamic[DT_PLTRELSZ],
- load_base);
- }
-}
-
-void Linker::ResolveImports() {
- // Resolve imports
- for (const auto& import : imports) {
- const auto& search = exports.find(import.first);
- if (search != exports.end()) {
- Memory::Write64(import.second.ea, search->second + import.second.addend);
- } else {
- LOG_ERROR(Loader, "Unresolved import: {}", import.first);
- }
- }
-}
-
-} // namespace Loader
diff --git a/src/core/loader/linker.h b/src/core/loader/linker.h
deleted file mode 100644
index 107625837..000000000
--- a/src/core/loader/linker.h
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <map>
-#include <string>
-#include "common/common_types.h"
-
-namespace Loader {
-
-class Linker {
-protected:
- struct Symbol {
- Symbol(std::string&& name, u64 value) : name(std::move(name)), value(value) {}
- std::string name;
- u64 value;
- };
-
- struct Import {
- VAddr ea;
- s64 addend;
- };
-
- void WriteRelocations(std::vector<u8>& program_image, const std::vector<Symbol>& symbols,
- u64 relocation_offset, u64 size, VAddr load_base);
- void Relocate(std::vector<u8>& program_image, u32 dynamic_section_offset, VAddr load_base);
-
- void ResolveImports();
-
- std::map<std::string, Import> imports;
- std::map<std::string, VAddr> exports;
-};
-
-} // namespace Loader
diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h
index bb925f4a6..869406b75 100644
--- a/src/core/loader/loader.h
+++ b/src/core/loader/loader.h
@@ -131,6 +131,12 @@ std::ostream& operator<<(std::ostream& os, ResultStatus status);
/// Interface for loading an application
class AppLoader : NonCopyable {
public:
+ struct LoadParameters {
+ s32 main_thread_priority;
+ u64 main_thread_stack_size;
+ };
+ using LoadResult = std::pair<ResultStatus, std::optional<LoadParameters>>;
+
explicit AppLoader(FileSys::VirtualFile file);
virtual ~AppLoader();
@@ -145,18 +151,7 @@ public:
* @param process The newly created process.
* @return The status result of the operation.
*/
- virtual ResultStatus Load(Kernel::Process& process) = 0;
-
- /**
- * Loads the system mode that this application needs.
- * This function defaults to 2 (96MB allocated to the application) if it can't read the
- * information.
- * @returns A pair with the optional system mode, and and the status.
- */
- virtual std::pair<std::optional<u32>, ResultStatus> LoadKernelSystemMode() {
- // 96MB allocated to the application.
- return std::make_pair(2, ResultStatus::Success);
- }
+ virtual LoadResult Load(Kernel::Process& process) = 0;
/**
* Get the code (typically .code section) of the application
diff --git a/src/core/loader/nax.cpp b/src/core/loader/nax.cpp
index 93a970d10..34efef09a 100644
--- a/src/core/loader/nax.cpp
+++ b/src/core/loader/nax.cpp
@@ -41,31 +41,37 @@ FileType AppLoader_NAX::GetFileType() const {
return IdentifyTypeImpl(*nax);
}
-ResultStatus AppLoader_NAX::Load(Kernel::Process& process) {
+AppLoader_NAX::LoadResult AppLoader_NAX::Load(Kernel::Process& process) {
if (is_loaded) {
- return ResultStatus::ErrorAlreadyLoaded;
+ return {ResultStatus::ErrorAlreadyLoaded, {}};
}
- if (nax->GetStatus() != ResultStatus::Success)
- return nax->GetStatus();
+ const auto nax_status = nax->GetStatus();
+ if (nax_status != ResultStatus::Success) {
+ return {nax_status, {}};
+ }
const auto nca = nax->AsNCA();
if (nca == nullptr) {
- if (!Core::Crypto::KeyManager::KeyFileExists(false))
- return ResultStatus::ErrorMissingProductionKeyFile;
- return ResultStatus::ErrorNAXInconvertibleToNCA;
+ if (!Core::Crypto::KeyManager::KeyFileExists(false)) {
+ return {ResultStatus::ErrorMissingProductionKeyFile, {}};
+ }
+
+ return {ResultStatus::ErrorNAXInconvertibleToNCA, {}};
}
- if (nca->GetStatus() != ResultStatus::Success)
- return nca->GetStatus();
+ const auto nca_status = nca->GetStatus();
+ if (nca_status != ResultStatus::Success) {
+ return {nca_status, {}};
+ }
const auto result = nca_loader->Load(process);
- if (result != ResultStatus::Success)
+ if (result.first != ResultStatus::Success) {
return result;
+ }
is_loaded = true;
-
- return ResultStatus::Success;
+ return result;
}
ResultStatus AppLoader_NAX::ReadRomFS(FileSys::VirtualFile& dir) {
diff --git a/src/core/loader/nax.h b/src/core/loader/nax.h
index f40079574..00f1659c1 100644
--- a/src/core/loader/nax.h
+++ b/src/core/loader/nax.h
@@ -33,7 +33,7 @@ public:
FileType GetFileType() const override;
- ResultStatus Load(Kernel::Process& process) override;
+ LoadResult Load(Kernel::Process& process) 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 ce8196fcf..b3f8f1083 100644
--- a/src/core/loader/nca.cpp
+++ b/src/core/loader/nca.cpp
@@ -30,36 +30,38 @@ FileType AppLoader_NCA::IdentifyType(const FileSys::VirtualFile& file) {
return FileType::Error;
}
-ResultStatus AppLoader_NCA::Load(Kernel::Process& process) {
+AppLoader_NCA::LoadResult AppLoader_NCA::Load(Kernel::Process& process) {
if (is_loaded) {
- return ResultStatus::ErrorAlreadyLoaded;
+ return {ResultStatus::ErrorAlreadyLoaded, {}};
}
const auto result = nca->GetStatus();
if (result != ResultStatus::Success) {
- return result;
+ return {result, {}};
}
- if (nca->GetType() != FileSys::NCAContentType::Program)
- return ResultStatus::ErrorNCANotProgram;
+ if (nca->GetType() != FileSys::NCAContentType::Program) {
+ return {ResultStatus::ErrorNCANotProgram, {}};
+ }
const auto exefs = nca->GetExeFS();
-
- if (exefs == nullptr)
- return ResultStatus::ErrorNoExeFS;
+ if (exefs == nullptr) {
+ return {ResultStatus::ErrorNoExeFS, {}};
+ }
directory_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(exefs, true);
const auto load_result = directory_loader->Load(process);
- if (load_result != ResultStatus::Success)
+ if (load_result.first != ResultStatus::Success) {
return load_result;
+ }
- if (nca->GetRomFS() != nullptr && nca->GetRomFS()->GetSize() > 0)
+ if (nca->GetRomFS() != nullptr && nca->GetRomFS()->GetSize() > 0) {
Service::FileSystem::RegisterRomFS(std::make_unique<FileSys::RomFSFactory>(*this));
+ }
is_loaded = true;
-
- return ResultStatus::Success;
+ return load_result;
}
ResultStatus AppLoader_NCA::ReadRomFS(FileSys::VirtualFile& dir) {
diff --git a/src/core/loader/nca.h b/src/core/loader/nca.h
index b9f077468..94f0ed677 100644
--- a/src/core/loader/nca.h
+++ b/src/core/loader/nca.h
@@ -33,7 +33,7 @@ public:
return IdentifyType(file);
}
- ResultStatus Load(Kernel::Process& process) override;
+ LoadResult Load(Kernel::Process& process) 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 4fad0c0dd..6a0ca389b 100644
--- a/src/core/loader/nro.cpp
+++ b/src/core/loader/nro.cpp
@@ -14,6 +14,7 @@
#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/process.h"
#include "core/hle/kernel/vm_manager.h"
#include "core/hle/service/filesystem/filesystem.h"
@@ -186,7 +187,7 @@ static bool LoadNroImpl(Kernel::Process& process, const std::vector<u8>& data,
program_image.resize(static_cast<u32>(program_image.size()) + bss_size);
// Load codeset for current process
- codeset.memory = std::make_shared<std::vector<u8>>(std::move(program_image));
+ codeset.memory = std::move(program_image);
process.LoadModule(std::move(codeset), load_base);
// Register module with GDBStub
@@ -200,25 +201,25 @@ bool AppLoader_NRO::LoadNro(Kernel::Process& process, const FileSys::VfsFile& fi
return LoadNroImpl(process, file.ReadAllBytes(), file.GetName(), load_base);
}
-ResultStatus AppLoader_NRO::Load(Kernel::Process& process) {
+AppLoader_NRO::LoadResult AppLoader_NRO::Load(Kernel::Process& process) {
if (is_loaded) {
- return ResultStatus::ErrorAlreadyLoaded;
+ return {ResultStatus::ErrorAlreadyLoaded, {}};
}
// Load NRO
const VAddr base_address = process.VMManager().GetCodeRegionBaseAddress();
if (!LoadNro(process, *file, base_address)) {
- return ResultStatus::ErrorLoadingNRO;
+ return {ResultStatus::ErrorLoadingNRO, {}};
}
- if (romfs != nullptr)
+ if (romfs != nullptr) {
Service::FileSystem::RegisterRomFS(std::make_unique<FileSys::RomFSFactory>(*this));
-
- process.Run(base_address, Kernel::THREADPRIO_DEFAULT, Memory::DEFAULT_STACK_SIZE);
+ }
is_loaded = true;
- return ResultStatus::Success;
+ return {ResultStatus::Success,
+ LoadParameters{Kernel::THREADPRIO_DEFAULT, 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 013d629c0..1ffdae805 100644
--- a/src/core/loader/nro.h
+++ b/src/core/loader/nro.h
@@ -4,10 +4,10 @@
#pragma once
+#include <memory>
#include <string>
#include <vector>
#include "common/common_types.h"
-#include "core/loader/linker.h"
#include "core/loader/loader.h"
namespace FileSys {
@@ -21,7 +21,7 @@ class Process;
namespace Loader {
/// Loads an NRO file
-class AppLoader_NRO final : public AppLoader, Linker {
+class AppLoader_NRO final : public AppLoader {
public:
explicit AppLoader_NRO(FileSys::VirtualFile file);
~AppLoader_NRO() override;
@@ -37,7 +37,7 @@ public:
return IdentifyType(file);
}
- ResultStatus Load(Kernel::Process& process) override;
+ LoadResult Load(Kernel::Process& process) 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 6ded0b707..80090b792 100644
--- a/src/core/loader/nso.cpp
+++ b/src/core/loader/nso.cpp
@@ -4,13 +4,17 @@
#include <cinttypes>
#include <vector>
-#include <lz4.h>
+
#include "common/common_funcs.h"
#include "common/file_util.h"
+#include "common/hex_util.h"
#include "common/logging/log.h"
+#include "common/lz4_compression.h"
#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/process.h"
#include "core/hle/kernel/vm_manager.h"
#include "core/loader/nso.h"
@@ -18,36 +22,8 @@
#include "core/settings.h"
namespace Loader {
-
-struct NsoSegmentHeader {
- u32_le offset;
- u32_le location;
- u32_le size;
- union {
- u32_le alignment;
- u32_le bss_size;
- };
-};
-static_assert(sizeof(NsoSegmentHeader) == 0x10, "NsoSegmentHeader has incorrect size.");
-
-struct NsoHeader {
- u32_le magic;
- u32_le version;
- INSERT_PADDING_WORDS(1);
- u8 flags;
- std::array<NsoSegmentHeader, 3> segments; // Text, RoData, Data (in that order)
- std::array<u8, 0x20> build_id;
- std::array<u32_le, 3> segments_compressed_size;
-
- bool IsSegmentCompressed(size_t segment_num) const {
- ASSERT_MSG(segment_num < 3, "Invalid segment {}", segment_num);
- return ((flags >> segment_num) & 1);
- }
-};
-static_assert(sizeof(NsoHeader) == 0x6c, "NsoHeader has incorrect size.");
-static_assert(std::is_trivially_copyable_v<NsoHeader>, "NsoHeader isn't trivially copyable.");
-
-struct ModHeader {
+namespace {
+struct MODHeader {
u32_le magic;
u32_le dynamic_offset;
u32_le bss_start_offset;
@@ -56,7 +32,28 @@ struct ModHeader {
u32_le eh_frame_hdr_end_offset;
u32_le module_offset; // Offset to runtime-generated module object. typically equal to .bss base
};
-static_assert(sizeof(ModHeader) == 0x1c, "ModHeader has incorrect size.");
+static_assert(sizeof(MODHeader) == 0x1c, "MODHeader has incorrect size.");
+
+std::vector<u8> DecompressSegment(const std::vector<u8>& compressed_data,
+ const NSOSegmentHeader& header) {
+ const std::vector<u8> uncompressed_data =
+ Common::Compression::DecompressDataLZ4(compressed_data, header.size);
+
+ ASSERT_MSG(uncompressed_data.size() == header.size, "{} != {}", header.size,
+ uncompressed_data.size());
+
+ return uncompressed_data;
+}
+
+constexpr u32 PageAlignSize(u32 size) {
+ return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK;
+}
+} // Anonymous namespace
+
+bool NSOHeader::IsSegmentCompressed(size_t segment_num) const {
+ ASSERT_MSG(segment_num < 3, "Invalid segment {}", segment_num);
+ return ((flags >> segment_num) & 1) != 0;
+}
AppLoader_NSO::AppLoader_NSO(FileSys::VirtualFile file) : AppLoader(std::move(file)) {}
@@ -73,38 +70,22 @@ FileType AppLoader_NSO::IdentifyType(const FileSys::VirtualFile& file) {
return FileType::NSO;
}
-static std::vector<u8> DecompressSegment(const std::vector<u8>& compressed_data,
- const NsoSegmentHeader& header) {
- std::vector<u8> uncompressed_data(header.size);
- const int bytes_uncompressed =
- LZ4_decompress_safe(reinterpret_cast<const char*>(compressed_data.data()),
- reinterpret_cast<char*>(uncompressed_data.data()),
- static_cast<int>(compressed_data.size()), header.size);
-
- ASSERT_MSG(bytes_uncompressed == static_cast<int>(header.size) &&
- bytes_uncompressed == static_cast<int>(uncompressed_data.size()),
- "{} != {} != {}", bytes_uncompressed, header.size, uncompressed_data.size());
-
- return uncompressed_data;
-}
-
-static constexpr u32 PageAlignSize(u32 size) {
- return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK;
-}
-
std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process,
const FileSys::VfsFile& file, VAddr load_base,
bool should_pass_arguments,
std::optional<FileSys::PatchManager> pm) {
- if (file.GetSize() < sizeof(NsoHeader))
+ if (file.GetSize() < sizeof(NSOHeader)) {
return {};
+ }
- NsoHeader nso_header{};
- if (sizeof(NsoHeader) != file.ReadObject(&nso_header))
+ NSOHeader nso_header{};
+ if (sizeof(NSOHeader) != file.ReadObject(&nso_header)) {
return {};
+ }
- if (nso_header.magic != Common::MakeMagic('N', 'S', 'O', '0'))
+ if (nso_header.magic != Common::MakeMagic('N', 'S', 'O', '0')) {
return {};
+ }
// Build program image
Kernel::CodeSet codeset;
@@ -140,10 +121,10 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process,
std::memcpy(&module_offset, program_image.data() + 4, sizeof(u32));
// Read MOD header
- ModHeader mod_header{};
+ MODHeader mod_header{};
// Default .bss to size in segment header if MOD0 section doesn't exist
u32 bss_size{PageAlignSize(nso_header.segments[2].bss_size)};
- std::memcpy(&mod_header, program_image.data() + module_offset, sizeof(ModHeader));
+ std::memcpy(&mod_header, program_image.data() + module_offset, sizeof(MODHeader));
const bool has_mod_header{mod_header.magic == Common::MakeMagic('M', 'O', 'D', '0')};
if (has_mod_header) {
// Resize program image to include .bss section and page align each section
@@ -155,17 +136,29 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process,
// Apply patches if necessary
if (pm && (pm->HasNSOPatch(nso_header.build_id) || Settings::values.dump_nso)) {
- std::vector<u8> pi_header(program_image.size() + 0x100);
- std::memcpy(pi_header.data(), &nso_header, sizeof(NsoHeader));
- std::memcpy(pi_header.data() + 0x100, program_image.data(), program_image.size());
+ std::vector<u8> pi_header;
+ pi_header.insert(pi_header.begin(), reinterpret_cast<u8*>(&nso_header),
+ reinterpret_cast<u8*>(&nso_header) + sizeof(NSOHeader));
+ pi_header.insert(pi_header.begin() + sizeof(NSOHeader), program_image.begin(),
+ program_image.end());
- pi_header = pm->PatchNSO(pi_header);
+ pi_header = pm->PatchNSO(pi_header, file.GetName());
- std::memcpy(program_image.data(), pi_header.data() + 0x100, program_image.size());
+ std::copy(pi_header.begin() + sizeof(NSOHeader), pi_header.end(), program_image.begin());
+ }
+
+ // Apply cheats if they exist and the program has a valid title ID
+ if (pm) {
+ auto& system = Core::System::GetInstance();
+ const auto cheats = pm->CreateCheatList(system, nso_header.build_id);
+ if (!cheats.empty()) {
+ system.RegisterCheatList(cheats, Common::HexToString(nso_header.build_id), load_base,
+ load_base + program_image.size());
+ }
}
// Load codeset for current process
- codeset.memory = std::make_shared<std::vector<u8>>(std::move(program_image));
+ codeset.memory = std::move(program_image);
process.LoadModule(std::move(codeset), load_base);
// Register module with GDBStub
@@ -174,22 +167,21 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process,
return load_base + image_size;
}
-ResultStatus AppLoader_NSO::Load(Kernel::Process& process) {
+AppLoader_NSO::LoadResult AppLoader_NSO::Load(Kernel::Process& process) {
if (is_loaded) {
- return ResultStatus::ErrorAlreadyLoaded;
+ return {ResultStatus::ErrorAlreadyLoaded, {}};
}
// Load module
const VAddr base_address = process.VMManager().GetCodeRegionBaseAddress();
if (!LoadModule(process, *file, base_address, true)) {
- return ResultStatus::ErrorLoadingNSO;
+ return {ResultStatus::ErrorLoadingNSO, {}};
}
LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", file->GetName(), base_address);
- process.Run(base_address, Kernel::THREADPRIO_DEFAULT, Memory::DEFAULT_STACK_SIZE);
-
is_loaded = true;
- return ResultStatus::Success;
+ return {ResultStatus::Success,
+ LoadParameters{Kernel::THREADPRIO_DEFAULT, Memory::DEFAULT_STACK_SIZE}};
}
} // namespace Loader
diff --git a/src/core/loader/nso.h b/src/core/loader/nso.h
index 135b6ea5a..fdce9191c 100644
--- a/src/core/loader/nso.h
+++ b/src/core/loader/nso.h
@@ -4,10 +4,12 @@
#pragma once
+#include <array>
#include <optional>
+#include <type_traits>
#include "common/common_types.h"
+#include "common/swap.h"
#include "core/file_sys/patch_manager.h"
-#include "core/loader/linker.h"
#include "core/loader/loader.h"
namespace Kernel {
@@ -16,6 +18,43 @@ class Process;
namespace Loader {
+struct NSOSegmentHeader {
+ u32_le offset;
+ u32_le location;
+ u32_le size;
+ union {
+ u32_le alignment;
+ u32_le bss_size;
+ };
+};
+static_assert(sizeof(NSOSegmentHeader) == 0x10, "NsoSegmentHeader has incorrect size.");
+
+struct NSOHeader {
+ using SHA256Hash = std::array<u8, 0x20>;
+
+ struct RODataRelativeExtent {
+ u32_le data_offset;
+ u32_le size;
+ };
+
+ u32_le magic;
+ u32_le version;
+ u32 reserved;
+ u32_le flags;
+ std::array<NSOSegmentHeader, 3> segments; // Text, RoData, Data (in that order)
+ std::array<u8, 0x20> build_id;
+ std::array<u32_le, 3> segments_compressed_size;
+ std::array<u8, 0x1C> padding;
+ RODataRelativeExtent api_info_extent;
+ RODataRelativeExtent dynstr_extent;
+ RODataRelativeExtent dynsyn_extent;
+ std::array<SHA256Hash, 3> segment_hashes;
+
+ bool IsSegmentCompressed(size_t segment_num) const;
+};
+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;
struct NSOArgumentHeader {
@@ -26,7 +65,7 @@ struct NSOArgumentHeader {
static_assert(sizeof(NSOArgumentHeader) == 0x20, "NSOArgumentHeader has incorrect size.");
/// Loads an NSO file
-class AppLoader_NSO final : public AppLoader, Linker {
+class AppLoader_NSO final : public AppLoader {
public:
explicit AppLoader_NSO(FileSys::VirtualFile file);
@@ -45,7 +84,7 @@ public:
VAddr load_base, bool should_pass_arguments,
std::optional<FileSys::PatchManager> pm = {});
- ResultStatus Load(Kernel::Process& process) override;
+ LoadResult Load(Kernel::Process& process) override;
};
} // namespace Loader
diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp
index 7da1f8960..ad56bbb38 100644
--- a/src/core/loader/nsp.cpp
+++ b/src/core/loader/nsp.cpp
@@ -72,37 +72,45 @@ FileType AppLoader_NSP::IdentifyType(const FileSys::VirtualFile& file) {
return FileType::Error;
}
-ResultStatus AppLoader_NSP::Load(Kernel::Process& process) {
+AppLoader_NSP::LoadResult AppLoader_NSP::Load(Kernel::Process& process) {
if (is_loaded) {
- return ResultStatus::ErrorAlreadyLoaded;
+ return {ResultStatus::ErrorAlreadyLoaded, {}};
}
- if (title_id == 0)
- return ResultStatus::ErrorNSPMissingProgramNCA;
+ if (title_id == 0) {
+ return {ResultStatus::ErrorNSPMissingProgramNCA, {}};
+ }
- if (nsp->GetStatus() != ResultStatus::Success)
- return nsp->GetStatus();
+ const auto nsp_status = nsp->GetStatus();
+ if (nsp_status != ResultStatus::Success) {
+ return {nsp_status, {}};
+ }
- if (nsp->GetProgramStatus(title_id) != ResultStatus::Success)
- return nsp->GetProgramStatus(title_id);
+ const auto nsp_program_status = nsp->GetProgramStatus(title_id);
+ if (nsp_program_status != ResultStatus::Success) {
+ return {nsp_program_status, {}};
+ }
if (nsp->GetNCA(title_id, FileSys::ContentRecordType::Program) == nullptr) {
- if (!Core::Crypto::KeyManager::KeyFileExists(false))
- return ResultStatus::ErrorMissingProductionKeyFile;
- return ResultStatus::ErrorNSPMissingProgramNCA;
+ if (!Core::Crypto::KeyManager::KeyFileExists(false)) {
+ return {ResultStatus::ErrorMissingProductionKeyFile, {}};
+ }
+
+ return {ResultStatus::ErrorNSPMissingProgramNCA, {}};
}
const auto result = secondary_loader->Load(process);
- if (result != ResultStatus::Success)
+ if (result.first != ResultStatus::Success) {
return result;
+ }
FileSys::VirtualFile update_raw;
- if (ReadUpdateRaw(update_raw) == ResultStatus::Success && update_raw != nullptr)
+ if (ReadUpdateRaw(update_raw) == ResultStatus::Success && update_raw != nullptr) {
Service::FileSystem::SetPackedUpdate(std::move(update_raw));
+ }
is_loaded = true;
-
- return ResultStatus::Success;
+ return result;
}
ResultStatus AppLoader_NSP::ReadRomFS(FileSys::VirtualFile& file) {
diff --git a/src/core/loader/nsp.h b/src/core/loader/nsp.h
index 953a1b508..85e870bdf 100644
--- a/src/core/loader/nsp.h
+++ b/src/core/loader/nsp.h
@@ -35,7 +35,7 @@ public:
return IdentifyType(file);
}
- ResultStatus Load(Kernel::Process& process) override;
+ LoadResult Load(Kernel::Process& process) 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 89f7bbf77..1e285a053 100644
--- a/src/core/loader/xci.cpp
+++ b/src/core/loader/xci.cpp
@@ -48,31 +48,35 @@ FileType AppLoader_XCI::IdentifyType(const FileSys::VirtualFile& file) {
return FileType::Error;
}
-ResultStatus AppLoader_XCI::Load(Kernel::Process& process) {
+AppLoader_XCI::LoadResult AppLoader_XCI::Load(Kernel::Process& process) {
if (is_loaded) {
- return ResultStatus::ErrorAlreadyLoaded;
+ return {ResultStatus::ErrorAlreadyLoaded, {}};
}
- if (xci->GetStatus() != ResultStatus::Success)
- return xci->GetStatus();
+ if (xci->GetStatus() != ResultStatus::Success) {
+ return {xci->GetStatus(), {}};
+ }
- if (xci->GetProgramNCAStatus() != ResultStatus::Success)
- return xci->GetProgramNCAStatus();
+ if (xci->GetProgramNCAStatus() != ResultStatus::Success) {
+ return {xci->GetProgramNCAStatus(), {}};
+ }
- if (!xci->HasProgramNCA() && !Core::Crypto::KeyManager::KeyFileExists(false))
- return ResultStatus::ErrorMissingProductionKeyFile;
+ if (!xci->HasProgramNCA() && !Core::Crypto::KeyManager::KeyFileExists(false)) {
+ return {ResultStatus::ErrorMissingProductionKeyFile, {}};
+ }
const auto result = nca_loader->Load(process);
- if (result != ResultStatus::Success)
+ if (result.first != ResultStatus::Success) {
return result;
+ }
FileSys::VirtualFile update_raw;
- if (ReadUpdateRaw(update_raw) == ResultStatus::Success && update_raw != nullptr)
+ if (ReadUpdateRaw(update_raw) == ResultStatus::Success && update_raw != nullptr) {
Service::FileSystem::SetPackedUpdate(std::move(update_raw));
+ }
is_loaded = true;
-
- return ResultStatus::Success;
+ return result;
}
ResultStatus AppLoader_XCI::ReadRomFS(FileSys::VirtualFile& file) {
diff --git a/src/core/loader/xci.h b/src/core/loader/xci.h
index d6995b61e..ae7145b14 100644
--- a/src/core/loader/xci.h
+++ b/src/core/loader/xci.h
@@ -22,7 +22,7 @@ class AppLoader_NCA;
class AppLoader_XCI final : public AppLoader {
public:
explicit AppLoader_XCI(FileSys::VirtualFile file);
- ~AppLoader_XCI();
+ ~AppLoader_XCI() override;
/**
* Returns the type of the file
@@ -35,7 +35,7 @@ public:
return IdentifyType(file);
}
- ResultStatus Load(Kernel::Process& process) override;
+ LoadResult Load(Kernel::Process& process) override;
ResultStatus ReadRomFS(FileSys::VirtualFile& file) override;
u64 ReadRomFSIVFCOffset() const override;
diff --git a/src/core/memory.cpp b/src/core/memory.cpp
index 6591c45d2..f18f6226b 100644
--- a/src/core/memory.cpp
+++ b/src/core/memory.cpp
@@ -10,6 +10,7 @@
#include "common/assert.h"
#include "common/common_types.h"
#include "common/logging/log.h"
+#include "common/page_table.h"
#include "common/swap.h"
#include "core/arm/arm_interface.h"
#include "core/core.h"
@@ -18,57 +19,35 @@
#include "core/hle/lock.h"
#include "core/memory.h"
#include "core/memory_setup.h"
+#include "video_core/gpu.h"
#include "video_core/renderer_base.h"
namespace Memory {
-static PageTable* current_page_table = nullptr;
+static Common::PageTable* current_page_table = nullptr;
-void SetCurrentPageTable(PageTable* page_table) {
- current_page_table = page_table;
+void SetCurrentPageTable(Kernel::Process& process) {
+ current_page_table = &process.VMManager().page_table;
- auto& system = Core::System::GetInstance();
- if (system.IsPoweredOn()) {
- system.ArmInterface(0).PageTableChanged();
- system.ArmInterface(1).PageTableChanged();
- system.ArmInterface(2).PageTableChanged();
- system.ArmInterface(3).PageTableChanged();
- }
-}
-
-PageTable* GetCurrentPageTable() {
- return current_page_table;
-}
-
-PageTable::PageTable() = default;
-
-PageTable::PageTable(std::size_t address_space_width_in_bits) {
- Resize(address_space_width_in_bits);
-}
+ const std::size_t address_space_width = process.VMManager().GetAddressSpaceWidth();
-PageTable::~PageTable() = default;
-
-void PageTable::Resize(std::size_t address_space_width_in_bits) {
- const std::size_t num_page_table_entries = 1ULL << (address_space_width_in_bits - PAGE_BITS);
-
- pointers.resize(num_page_table_entries);
- attributes.resize(num_page_table_entries);
-
- // The default is a 39-bit address space, which causes an initial 1GB allocation size. If the
- // vector size is subsequently decreased (via resize), the vector might not automatically
- // actually reallocate/resize its underlying allocation, which wastes up to ~800 MB for
- // 36-bit titles. Call shrink_to_fit to reduce capacity to what's actually in use.
-
- pointers.shrink_to_fit();
- attributes.shrink_to_fit();
+ auto& system = Core::System::GetInstance();
+ system.ArmInterface(0).PageTableChanged(*current_page_table, address_space_width);
+ system.ArmInterface(1).PageTableChanged(*current_page_table, address_space_width);
+ system.ArmInterface(2).PageTableChanged(*current_page_table, address_space_width);
+ system.ArmInterface(3).PageTableChanged(*current_page_table, address_space_width);
}
-static void MapPages(PageTable& page_table, VAddr base, u64 size, u8* memory, PageType type) {
+static void MapPages(Common::PageTable& page_table, VAddr base, u64 size, u8* memory,
+ Common::PageType type) {
LOG_DEBUG(HW_Memory, "Mapping {} onto {:016X}-{:016X}", fmt::ptr(memory), base * PAGE_SIZE,
(base + size) * PAGE_SIZE);
- RasterizerFlushVirtualRegion(base << PAGE_BITS, size * PAGE_SIZE,
- FlushMode::FlushAndInvalidate);
+ // During boot, current_page_table might not be set yet, in which case we need not flush
+ if (Core::System::GetInstance().IsPoweredOn()) {
+ Core::System::GetInstance().GPU().FlushAndInvalidateRegion(base << PAGE_BITS,
+ size * PAGE_SIZE);
+ }
VAddr end = base + size;
ASSERT_MSG(end <= page_table.pointers.size(), "out of range mapping at {:016X}",
@@ -88,41 +67,47 @@ static void MapPages(PageTable& page_table, VAddr base, u64 size, u8* memory, Pa
}
}
-void MapMemoryRegion(PageTable& page_table, VAddr base, u64 size, u8* target) {
+void MapMemoryRegion(Common::PageTable& page_table, VAddr base, u64 size, u8* target) {
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, target, PageType::Memory);
+ MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, target, Common::PageType::Memory);
}
-void MapIoRegion(PageTable& page_table, VAddr base, u64 size, MemoryHookPointer mmio_handler) {
+void MapIoRegion(Common::PageTable& page_table, VAddr base, u64 size,
+ Common::MemoryHookPointer mmio_handler) {
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, nullptr, PageType::Special);
+ MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, nullptr, Common::PageType::Special);
auto interval = boost::icl::discrete_interval<VAddr>::closed(base, base + size - 1);
- SpecialRegion region{SpecialRegion::Type::IODevice, std::move(mmio_handler)};
- page_table.special_regions.add(std::make_pair(interval, std::set<SpecialRegion>{region}));
+ Common::SpecialRegion region{Common::SpecialRegion::Type::IODevice, std::move(mmio_handler)};
+ page_table.special_regions.add(
+ std::make_pair(interval, std::set<Common::SpecialRegion>{region}));
}
-void UnmapRegion(PageTable& page_table, VAddr base, u64 size) {
+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, nullptr, PageType::Unmapped);
+ MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, nullptr, Common::PageType::Unmapped);
auto interval = boost::icl::discrete_interval<VAddr>::closed(base, base + size - 1);
page_table.special_regions.erase(interval);
}
-void AddDebugHook(PageTable& page_table, VAddr base, u64 size, MemoryHookPointer hook) {
+void AddDebugHook(Common::PageTable& page_table, VAddr base, u64 size,
+ Common::MemoryHookPointer hook) {
auto interval = boost::icl::discrete_interval<VAddr>::closed(base, base + size - 1);
- SpecialRegion region{SpecialRegion::Type::DebugHook, std::move(hook)};
- page_table.special_regions.add(std::make_pair(interval, std::set<SpecialRegion>{region}));
+ Common::SpecialRegion region{Common::SpecialRegion::Type::DebugHook, std::move(hook)};
+ page_table.special_regions.add(
+ std::make_pair(interval, std::set<Common::SpecialRegion>{region}));
}
-void RemoveDebugHook(PageTable& page_table, VAddr base, u64 size, MemoryHookPointer hook) {
+void RemoveDebugHook(Common::PageTable& page_table, VAddr base, u64 size,
+ Common::MemoryHookPointer hook) {
auto interval = boost::icl::discrete_interval<VAddr>::closed(base, base + size - 1);
- SpecialRegion region{SpecialRegion::Type::DebugHook, std::move(hook)};
- page_table.special_regions.subtract(std::make_pair(interval, std::set<SpecialRegion>{region}));
+ Common::SpecialRegion region{Common::SpecialRegion::Type::DebugHook, std::move(hook)};
+ page_table.special_regions.subtract(
+ std::make_pair(interval, std::set<Common::SpecialRegion>{region}));
}
/**
@@ -171,19 +156,19 @@ T Read(const VAddr vaddr) {
return value;
}
- PageType type = current_page_table->attributes[vaddr >> PAGE_BITS];
+ Common::PageType type = current_page_table->attributes[vaddr >> PAGE_BITS];
switch (type) {
- case PageType::Unmapped:
+ case Common::PageType::Unmapped:
LOG_ERROR(HW_Memory, "Unmapped Read{} @ 0x{:08X}", sizeof(T) * 8, vaddr);
return 0;
- case PageType::Memory:
+ case Common::PageType::Memory:
ASSERT_MSG(false, "Mapped memory page without a pointer @ {:016X}", vaddr);
break;
- case PageType::RasterizerCachedMemory: {
- RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::Flush);
-
+ case Common::PageType::RasterizerCachedMemory: {
+ auto host_ptr{GetPointerFromVMA(vaddr)};
+ Core::System::GetInstance().GPU().FlushRegion(ToCacheAddr(host_ptr), sizeof(T));
T value;
- std::memcpy(&value, GetPointerFromVMA(vaddr), sizeof(T));
+ std::memcpy(&value, host_ptr, sizeof(T));
return value;
}
default:
@@ -201,18 +186,19 @@ void Write(const VAddr vaddr, const T data) {
return;
}
- PageType type = current_page_table->attributes[vaddr >> PAGE_BITS];
+ Common::PageType type = current_page_table->attributes[vaddr >> PAGE_BITS];
switch (type) {
- case PageType::Unmapped:
+ case Common::PageType::Unmapped:
LOG_ERROR(HW_Memory, "Unmapped Write{} 0x{:08X} @ 0x{:016X}", sizeof(data) * 8,
static_cast<u32>(data), vaddr);
return;
- case PageType::Memory:
+ case Common::PageType::Memory:
ASSERT_MSG(false, "Mapped memory page without a pointer @ {:016X}", vaddr);
break;
- case PageType::RasterizerCachedMemory: {
- RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::Invalidate);
- std::memcpy(GetPointerFromVMA(vaddr), &data, sizeof(T));
+ case Common::PageType::RasterizerCachedMemory: {
+ auto host_ptr{GetPointerFromVMA(vaddr)};
+ Core::System::GetInstance().GPU().InvalidateRegion(ToCacheAddr(host_ptr), sizeof(T));
+ std::memcpy(host_ptr, &data, sizeof(T));
break;
}
default:
@@ -227,10 +213,10 @@ bool IsValidVirtualAddress(const Kernel::Process& process, const VAddr vaddr) {
if (page_pointer)
return true;
- if (page_table.attributes[vaddr >> PAGE_BITS] == PageType::RasterizerCachedMemory)
+ if (page_table.attributes[vaddr >> PAGE_BITS] == Common::PageType::RasterizerCachedMemory)
return true;
- if (page_table.attributes[vaddr >> PAGE_BITS] != PageType::Special)
+ if (page_table.attributes[vaddr >> PAGE_BITS] != Common::PageType::Special)
return false;
return false;
@@ -250,7 +236,8 @@ u8* GetPointer(const VAddr vaddr) {
return page_pointer + (vaddr & PAGE_MASK);
}
- if (current_page_table->attributes[vaddr >> PAGE_BITS] == PageType::RasterizerCachedMemory) {
+ if (current_page_table->attributes[vaddr >> PAGE_BITS] ==
+ Common::PageType::RasterizerCachedMemory) {
return GetPointerFromVMA(vaddr);
}
@@ -284,20 +271,20 @@ void RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached) {
u64 num_pages = ((vaddr + size - 1) >> PAGE_BITS) - (vaddr >> PAGE_BITS) + 1;
for (unsigned i = 0; i < num_pages; ++i, vaddr += PAGE_SIZE) {
- PageType& page_type = current_page_table->attributes[vaddr >> PAGE_BITS];
+ Common::PageType& page_type = current_page_table->attributes[vaddr >> PAGE_BITS];
if (cached) {
// Switch page type to cached if now cached
switch (page_type) {
- case PageType::Unmapped:
+ case Common::PageType::Unmapped:
// It is not necessary for a process to have this region mapped into its address
// space, for example, a system module need not have a VRAM mapping.
break;
- case PageType::Memory:
- page_type = PageType::RasterizerCachedMemory;
+ case Common::PageType::Memory:
+ page_type = Common::PageType::RasterizerCachedMemory;
current_page_table->pointers[vaddr >> PAGE_BITS] = nullptr;
break;
- case PageType::RasterizerCachedMemory:
+ case Common::PageType::RasterizerCachedMemory:
// There can be more than one GPU region mapped per CPU region, so it's common that
// this area is already marked as cached.
break;
@@ -307,23 +294,23 @@ void RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached) {
} else {
// Switch page type to uncached if now uncached
switch (page_type) {
- case PageType::Unmapped:
+ case Common::PageType::Unmapped:
// It is not necessary for a process to have this region mapped into its address
// space, for example, a system module need not have a VRAM mapping.
break;
- case PageType::Memory:
+ case Common::PageType::Memory:
// There can be more than one GPU region mapped per CPU region, so it's common that
// this area is already unmarked as cached.
break;
- case PageType::RasterizerCachedMemory: {
+ case Common::PageType::RasterizerCachedMemory: {
u8* pointer = GetPointerFromVMA(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 = PageType::Unmapped;
+ page_type = Common::PageType::Unmapped;
} else {
- page_type = PageType::Memory;
+ page_type = Common::PageType::Memory;
current_page_table->pointers[vaddr >> PAGE_BITS] = pointer;
}
break;
@@ -335,47 +322,6 @@ void RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached) {
}
}
-void RasterizerFlushVirtualRegion(VAddr start, u64 size, FlushMode mode) {
- auto& system_instance = Core::System::GetInstance();
-
- // Since pages are unmapped on shutdown after video core is shutdown, the renderer may be
- // null here
- if (!system_instance.IsPoweredOn()) {
- return;
- }
-
- const VAddr end = start + size;
-
- const auto CheckRegion = [&](VAddr region_start, VAddr region_end) {
- if (start >= region_end || end <= region_start) {
- // No overlap with region
- return;
- }
-
- const VAddr overlap_start = std::max(start, region_start);
- const VAddr overlap_end = std::min(end, region_end);
- const VAddr overlap_size = overlap_end - overlap_start;
-
- auto& gpu = system_instance.GPU();
- switch (mode) {
- case FlushMode::Flush:
- gpu.FlushRegion(overlap_start, overlap_size);
- break;
- case FlushMode::Invalidate:
- gpu.InvalidateRegion(overlap_start, overlap_size);
- break;
- case FlushMode::FlushAndInvalidate:
- gpu.FlushAndInvalidateRegion(overlap_start, overlap_size);
- break;
- }
- };
-
- const auto& vm_manager = Core::CurrentProcess()->VMManager();
-
- CheckRegion(vm_manager.GetCodeRegionBaseAddress(), vm_manager.GetCodeRegionEndAddress());
- CheckRegion(vm_manager.GetHeapRegionBaseAddress(), vm_manager.GetHeapRegionEndAddress());
-}
-
u8 Read8(const VAddr addr) {
return Read<u8>(addr);
}
@@ -406,24 +352,24 @@ void ReadBlock(const Kernel::Process& process, const VAddr src_addr, void* dest_
const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset);
switch (page_table.attributes[page_index]) {
- case PageType::Unmapped: {
+ case Common::PageType::Unmapped: {
LOG_ERROR(HW_Memory,
"Unmapped ReadBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})",
current_vaddr, src_addr, size);
std::memset(dest_buffer, 0, copy_amount);
break;
}
- case PageType::Memory: {
+ case Common::PageType::Memory: {
DEBUG_ASSERT(page_table.pointers[page_index]);
const u8* src_ptr = page_table.pointers[page_index] + page_offset;
std::memcpy(dest_buffer, src_ptr, copy_amount);
break;
}
- case PageType::RasterizerCachedMemory: {
- RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount),
- FlushMode::Flush);
- std::memcpy(dest_buffer, GetPointerFromVMA(process, current_vaddr), copy_amount);
+ case Common::PageType::RasterizerCachedMemory: {
+ const auto& host_ptr{GetPointerFromVMA(process, current_vaddr)};
+ Core::System::GetInstance().GPU().FlushRegion(ToCacheAddr(host_ptr), copy_amount);
+ std::memcpy(dest_buffer, host_ptr, copy_amount);
break;
}
default:
@@ -470,23 +416,23 @@ void WriteBlock(const Kernel::Process& process, const VAddr dest_addr, const voi
const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset);
switch (page_table.attributes[page_index]) {
- case PageType::Unmapped: {
+ case Common::PageType::Unmapped: {
LOG_ERROR(HW_Memory,
"Unmapped WriteBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})",
current_vaddr, dest_addr, size);
break;
}
- case PageType::Memory: {
+ case Common::PageType::Memory: {
DEBUG_ASSERT(page_table.pointers[page_index]);
u8* dest_ptr = page_table.pointers[page_index] + page_offset;
std::memcpy(dest_ptr, src_buffer, copy_amount);
break;
}
- case PageType::RasterizerCachedMemory: {
- RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount),
- FlushMode::Invalidate);
- std::memcpy(GetPointerFromVMA(process, current_vaddr), src_buffer, copy_amount);
+ case Common::PageType::RasterizerCachedMemory: {
+ const auto& host_ptr{GetPointerFromVMA(process, current_vaddr)};
+ Core::System::GetInstance().GPU().InvalidateRegion(ToCacheAddr(host_ptr), copy_amount);
+ std::memcpy(host_ptr, src_buffer, copy_amount);
break;
}
default:
@@ -516,23 +462,23 @@ void ZeroBlock(const Kernel::Process& process, const VAddr dest_addr, const std:
const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset);
switch (page_table.attributes[page_index]) {
- case PageType::Unmapped: {
+ case Common::PageType::Unmapped: {
LOG_ERROR(HW_Memory,
"Unmapped ZeroBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})",
current_vaddr, dest_addr, size);
break;
}
- case PageType::Memory: {
+ case Common::PageType::Memory: {
DEBUG_ASSERT(page_table.pointers[page_index]);
u8* dest_ptr = page_table.pointers[page_index] + page_offset;
std::memset(dest_ptr, 0, copy_amount);
break;
}
- case PageType::RasterizerCachedMemory: {
- RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount),
- FlushMode::Invalidate);
- std::memset(GetPointerFromVMA(process, current_vaddr), 0, copy_amount);
+ case Common::PageType::RasterizerCachedMemory: {
+ const auto& host_ptr{GetPointerFromVMA(process, current_vaddr)};
+ Core::System::GetInstance().GPU().InvalidateRegion(ToCacheAddr(host_ptr), copy_amount);
+ std::memset(host_ptr, 0, copy_amount);
break;
}
default:
@@ -558,23 +504,23 @@ void CopyBlock(const Kernel::Process& process, VAddr dest_addr, VAddr src_addr,
const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset);
switch (page_table.attributes[page_index]) {
- case PageType::Unmapped: {
+ case Common::PageType::Unmapped: {
LOG_ERROR(HW_Memory,
"Unmapped CopyBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})",
current_vaddr, src_addr, size);
ZeroBlock(process, dest_addr, copy_amount);
break;
}
- case PageType::Memory: {
+ case Common::PageType::Memory: {
DEBUG_ASSERT(page_table.pointers[page_index]);
const u8* src_ptr = page_table.pointers[page_index] + page_offset;
WriteBlock(process, dest_addr, src_ptr, copy_amount);
break;
}
- case PageType::RasterizerCachedMemory: {
- RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount),
- FlushMode::Flush);
- WriteBlock(process, dest_addr, GetPointerFromVMA(process, current_vaddr), copy_amount);
+ case Common::PageType::RasterizerCachedMemory: {
+ const auto& host_ptr{GetPointerFromVMA(process, current_vaddr)};
+ Core::System::GetInstance().GPU().FlushRegion(ToCacheAddr(host_ptr), copy_amount);
+ WriteBlock(process, dest_addr, host_ptr, copy_amount);
break;
}
default:
diff --git a/src/core/memory.h b/src/core/memory.h
index 1acf5ce8c..04e2c5f1d 100644
--- a/src/core/memory.h
+++ b/src/core/memory.h
@@ -6,11 +6,11 @@
#include <cstddef>
#include <string>
-#include <tuple>
-#include <vector>
-#include <boost/icl/interval_map.hpp>
#include "common/common_types.h"
-#include "core/memory_hook.h"
+
+namespace Common {
+struct PageTable;
+}
namespace Kernel {
class Process;
@@ -26,83 +26,8 @@ constexpr std::size_t PAGE_BITS = 12;
constexpr u64 PAGE_SIZE = 1ULL << PAGE_BITS;
constexpr u64 PAGE_MASK = PAGE_SIZE - 1;
-enum class PageType : u8 {
- /// Page is unmapped and should cause an access error.
- Unmapped,
- /// Page is mapped to regular memory. This is the only type you can get pointers to.
- Memory,
- /// 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,
-};
-
-struct SpecialRegion {
- enum class Type {
- DebugHook,
- IODevice,
- } type;
-
- MemoryHookPointer handler;
-
- bool operator<(const SpecialRegion& other) const {
- return std::tie(type, handler) < std::tie(other.type, other.handler);
- }
-
- bool operator==(const SpecialRegion& other) const {
- return std::tie(type, handler) == std::tie(other.type, other.handler);
- }
-};
-
-/**
- * A (reasonably) fast way of allowing switchable and remappable process address spaces. It loosely
- * mimics the way a real CPU page table works.
- */
-struct PageTable {
- explicit PageTable();
- explicit PageTable(std::size_t address_space_width_in_bits);
- ~PageTable();
-
- /**
- * Resizes the page table to be able to accomodate enough pages within
- * a given address space.
- *
- * @param address_space_width_in_bits The address size width in bits.
- */
- void Resize(std::size_t address_space_width_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`.
- */
- std::vector<u8*> pointers;
-
- /**
- * Contains MMIO handlers that back memory regions whose entries in the `attribute` vector is
- * of type `Special`.
- */
- boost::icl::interval_map<VAddr, std::set<SpecialRegion>> special_regions;
-
- /**
- * Vector of fine grained page attributes. If it is set to any value other than `Memory`, then
- * the corresponding entry in `pointers` MUST be set to null.
- */
- std::vector<PageType> attributes;
-};
-
/// Virtual user-space memory regions
enum : VAddr {
- /// Read-only page containing kernel and system configuration values.
- CONFIG_MEMORY_VADDR = 0x1FF80000,
- CONFIG_MEMORY_SIZE = 0x00001000,
- CONFIG_MEMORY_VADDR_END = CONFIG_MEMORY_VADDR + CONFIG_MEMORY_SIZE,
-
- /// Usually read-only page containing mostly values read from hardware.
- SHARED_PAGE_VADDR = 0x1FF81000,
- SHARED_PAGE_SIZE = 0x00001000,
- SHARED_PAGE_VADDR_END = SHARED_PAGE_VADDR + SHARED_PAGE_SIZE,
-
/// TLS (Thread-Local Storage) related.
TLS_ENTRY_SIZE = 0x200,
@@ -115,9 +40,9 @@ enum : VAddr {
KERNEL_REGION_END = KERNEL_REGION_VADDR + KERNEL_REGION_SIZE,
};
-/// Currently active page table
-void SetCurrentPageTable(PageTable* page_table);
-PageTable* GetCurrentPageTable();
+/// Changes the currently active page table to that of
+/// the given process instance.
+void SetCurrentPageTable(Kernel::Process& process);
/// Determines if the given VAddr is valid for the specified process.
bool IsValidVirtualAddress(const Kernel::Process& process, VAddr vaddr);
@@ -147,24 +72,9 @@ u8* GetPointer(VAddr vaddr);
std::string ReadCString(VAddr vaddr, std::size_t max_length);
-enum class FlushMode {
- /// Write back modified surfaces to RAM
- Flush,
- /// Remove region from the cache
- Invalidate,
- /// Write back modified surfaces to RAM, and also remove them from the cache
- FlushAndInvalidate,
-};
-
/**
* Mark each page touching the region as cached.
*/
void RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached);
-/**
- * Flushes and invalidates any externally cached rasterizer resources touching the given virtual
- * address region.
- */
-void RasterizerFlushVirtualRegion(VAddr start, u64 size, FlushMode mode);
-
} // namespace Memory
diff --git a/src/core/memory_setup.h b/src/core/memory_setup.h
index 9a1a4f4be..5225ee8e2 100644
--- a/src/core/memory_setup.h
+++ b/src/core/memory_setup.h
@@ -5,7 +5,11 @@
#pragma once
#include "common/common_types.h"
-#include "core/memory_hook.h"
+#include "common/memory_hook.h"
+
+namespace Common {
+struct PageTable;
+}
namespace Memory {
@@ -17,7 +21,7 @@ namespace Memory {
* @param size The amount of bytes to map. Must be page-aligned.
* @param target Buffer with the memory backing the mapping. Must be of length at least `size`.
*/
-void MapMemoryRegion(PageTable& page_table, VAddr base, u64 size, u8* target);
+void MapMemoryRegion(Common::PageTable& page_table, VAddr base, u64 size, u8* target);
/**
* Maps a region of the emulated process address space as a IO region.
@@ -26,11 +30,14 @@ void MapMemoryRegion(PageTable& page_table, VAddr base, u64 size, u8* target);
* @param size The amount of bytes to map. Must be page-aligned.
* @param mmio_handler The handler that backs the mapping.
*/
-void MapIoRegion(PageTable& page_table, VAddr base, u64 size, MemoryHookPointer mmio_handler);
+void MapIoRegion(Common::PageTable& page_table, VAddr base, u64 size,
+ Common::MemoryHookPointer mmio_handler);
-void UnmapRegion(PageTable& page_table, VAddr base, u64 size);
+void UnmapRegion(Common::PageTable& page_table, VAddr base, u64 size);
-void AddDebugHook(PageTable& page_table, VAddr base, u64 size, MemoryHookPointer hook);
-void RemoveDebugHook(PageTable& page_table, VAddr base, u64 size, MemoryHookPointer hook);
+void AddDebugHook(Common::PageTable& page_table, VAddr base, u64 size,
+ Common::MemoryHookPointer hook);
+void RemoveDebugHook(Common::PageTable& page_table, VAddr base, u64 size,
+ Common::MemoryHookPointer hook);
} // namespace Memory
diff --git a/src/core/perf_stats.cpp b/src/core/perf_stats.cpp
index c716a462b..4afd6c8a3 100644
--- a/src/core/perf_stats.cpp
+++ b/src/core/perf_stats.cpp
@@ -18,13 +18,13 @@ using std::chrono::microseconds;
namespace Core {
void PerfStats::BeginSystemFrame() {
- std::lock_guard<std::mutex> lock(object_mutex);
+ std::lock_guard lock{object_mutex};
frame_begin = Clock::now();
}
void PerfStats::EndSystemFrame() {
- std::lock_guard<std::mutex> lock(object_mutex);
+ std::lock_guard lock{object_mutex};
auto frame_end = Clock::now();
accumulated_frametime += frame_end - frame_begin;
@@ -35,13 +35,13 @@ void PerfStats::EndSystemFrame() {
}
void PerfStats::EndGameFrame() {
- std::lock_guard<std::mutex> lock(object_mutex);
+ std::lock_guard lock{object_mutex};
game_frames += 1;
}
PerfStatsResults PerfStats::GetAndResetStats(microseconds current_system_time_us) {
- std::lock_guard<std::mutex> lock(object_mutex);
+ std::lock_guard lock{object_mutex};
const auto now = Clock::now();
// Walltime elapsed since stats were reset
@@ -67,7 +67,7 @@ PerfStatsResults PerfStats::GetAndResetStats(microseconds current_system_time_us
}
double PerfStats::GetLastFrameTimeScale() {
- std::lock_guard<std::mutex> lock(object_mutex);
+ std::lock_guard lock{object_mutex};
constexpr double FRAME_LENGTH = 1.0 / 60;
return duration_cast<DoubleSecs>(previous_frame_length).count() / FRAME_LENGTH;
diff --git a/src/core/settings.cpp b/src/core/settings.cpp
index 2e232e1e7..6d32ebea3 100644
--- a/src/core/settings.cpp
+++ b/src/core/settings.cpp
@@ -82,7 +82,6 @@ void LogSetting(const std::string& name, const T& value) {
void LogSettings() {
LOG_INFO(Config, "yuzu Configuration:");
LogSetting("System_UseDockedMode", Settings::values.use_docked_mode);
- LogSetting("System_EnableNfc", Settings::values.enable_nfc);
LogSetting("System_RngSeed", Settings::values.rng_seed.value_or(0));
LogSetting("System_CurrentUser", Settings::values.current_user);
LogSetting("System_LanguageIndex", Settings::values.language_index);
@@ -91,7 +90,10 @@ void LogSettings() {
LogSetting("Renderer_UseResolutionFactor", Settings::values.resolution_factor);
LogSetting("Renderer_UseFrameLimit", Settings::values.use_frame_limit);
LogSetting("Renderer_FrameLimit", Settings::values.frame_limit);
+ LogSetting("Renderer_UseDiskShaderCache", Settings::values.use_disk_shader_cache);
LogSetting("Renderer_UseAccurateGpuEmulation", Settings::values.use_accurate_gpu_emulation);
+ LogSetting("Renderer_UseAsynchronousGpuEmulation",
+ Settings::values.use_asynchronous_gpu_emulation);
LogSetting("Audio_OutputEngine", Settings::values.sink_id);
LogSetting("Audio_EnableAudioStretching", Settings::values.enable_audio_stretching);
LogSetting("Audio_OutputDevice", Settings::values.audio_device_id);
diff --git a/src/core/settings.h b/src/core/settings.h
index cdfb2f742..b84390745 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -349,7 +349,6 @@ struct TouchscreenInput {
struct Values {
// System
bool use_docked_mode;
- bool enable_nfc;
std::optional<u32> rng_seed;
// Measured in seconds since epoch
std::optional<std::chrono::seconds> custom_rtc;
@@ -394,6 +393,7 @@ struct Values {
bool use_disk_shader_cache;
bool use_accurate_gpu_emulation;
bool use_asynchronous_gpu_emulation;
+ bool force_30fps_mode;
float bg_red;
float bg_green;
diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp
index e1db06811..90d06830f 100644
--- a/src/core/telemetry_session.cpp
+++ b/src/core/telemetry_session.cpp
@@ -12,7 +12,6 @@
#include "common/file_util.h"
#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/loader/loader.h"
@@ -101,13 +100,30 @@ bool VerifyLogin(const std::string& username, const std::string& token) {
#endif
}
-TelemetrySession::TelemetrySession() {
+TelemetrySession::TelemetrySession() = default;
+
+TelemetrySession::~TelemetrySession() {
+ // Log one-time session end information
+ const s64 shutdown_time{std::chrono::duration_cast<std::chrono::milliseconds>(
+ std::chrono::system_clock::now().time_since_epoch())
+ .count()};
+ AddField(Telemetry::FieldType::Session, "Shutdown_Time", shutdown_time);
+
#ifdef ENABLE_WEB_SERVICE
- backend = std::make_unique<WebService::TelemetryJson>(
+ auto backend = std::make_unique<WebService::TelemetryJson>(
Settings::values.web_api_url, Settings::values.yuzu_username, Settings::values.yuzu_token);
#else
- backend = std::make_unique<Telemetry::NullVisitor>();
+ auto backend = std::make_unique<Telemetry::NullVisitor>();
#endif
+
+ // Complete the session, submitting to the web service backend if necessary
+ field_collection.Accept(*backend);
+ if (Settings::values.enable_telemetry) {
+ backend->Complete();
+ }
+}
+
+void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader) {
// Log one-time top-level information
AddField(Telemetry::FieldType::None, "TelemetryId", GetTelemetryId());
@@ -118,26 +134,28 @@ TelemetrySession::TelemetrySession() {
AddField(Telemetry::FieldType::Session, "Init_Time", init_time);
u64 program_id{};
- const Loader::ResultStatus res{System::GetInstance().GetAppLoader().ReadProgramId(program_id)};
+ const Loader::ResultStatus res{app_loader.ReadProgramId(program_id)};
if (res == Loader::ResultStatus::Success) {
const std::string formatted_program_id{fmt::format("{:016X}", program_id)};
AddField(Telemetry::FieldType::Session, "ProgramId", formatted_program_id);
std::string name;
- System::GetInstance().GetAppLoader().ReadTitle(name);
+ app_loader.ReadTitle(name);
if (name.empty()) {
auto [nacp, icon_file] = FileSys::PatchManager(program_id).GetControlMetadata();
- if (nacp != nullptr)
+ if (nacp != nullptr) {
name = nacp->GetApplicationName();
+ }
}
- if (!name.empty())
+ if (!name.empty()) {
AddField(Telemetry::FieldType::Session, "ProgramName", name);
+ }
}
AddField(Telemetry::FieldType::Session, "ProgramFormat",
- static_cast<u8>(System::GetInstance().GetAppLoader().GetFileType()));
+ static_cast<u8>(app_loader.GetFileType()));
// Log application information
Telemetry::AppendBuildInfo(field_collection);
@@ -168,24 +186,10 @@ TelemetrySession::TelemetrySession() {
Settings::values.use_docked_mode);
}
-TelemetrySession::~TelemetrySession() {
- // Log one-time session end information
- const s64 shutdown_time{std::chrono::duration_cast<std::chrono::milliseconds>(
- std::chrono::system_clock::now().time_since_epoch())
- .count()};
- AddField(Telemetry::FieldType::Session, "Shutdown_Time", shutdown_time);
-
- // Complete the session, submitting to web service if necessary
- // This is just a placeholder to wrap up the session once the core completes and this is
- // destroyed. This will be moved elsewhere once we are actually doing real I/O with the service.
- field_collection.Accept(*backend);
- if (Settings::values.enable_telemetry)
- backend->Complete();
- backend = nullptr;
-}
-
bool TelemetrySession::SubmitTestcase() {
#ifdef ENABLE_WEB_SERVICE
+ auto backend = std::make_unique<WebService::TelemetryJson>(
+ Settings::values.web_api_url, Settings::values.yuzu_username, Settings::values.yuzu_token);
field_collection.Accept(*backend);
return backend->SubmitTestcase();
#else
diff --git a/src/core/telemetry_session.h b/src/core/telemetry_session.h
index 023612b79..17ac22377 100644
--- a/src/core/telemetry_session.h
+++ b/src/core/telemetry_session.h
@@ -4,10 +4,13 @@
#pragma once
-#include <memory>
#include <string>
#include "common/telemetry.h"
+namespace Loader {
+class AppLoader;
+}
+
namespace Core {
/**
@@ -15,11 +18,33 @@ namespace Core {
* session, logging any one-time fields. Interfaces with the telemetry backend used for submitting
* data to the web service. Submits session data on close.
*/
-class TelemetrySession : NonCopyable {
+class TelemetrySession {
public:
- TelemetrySession();
+ explicit TelemetrySession();
~TelemetrySession();
+ TelemetrySession(const TelemetrySession&) = delete;
+ TelemetrySession& operator=(const TelemetrySession&) = delete;
+
+ TelemetrySession(TelemetrySession&&) = delete;
+ TelemetrySession& operator=(TelemetrySession&&) = delete;
+
+ /**
+ * Adds the initial telemetry info necessary when starting up a title.
+ *
+ * This includes information such as:
+ * - Telemetry ID
+ * - Initialization time
+ * - Title ID
+ * - Title name
+ * - Title file format
+ * - Miscellaneous settings values.
+ *
+ * @param app_loader The application loader to use to retrieve
+ * title-specific information.
+ */
+ void AddInitialInfo(Loader::AppLoader& app_loader);
+
/**
* Wrapper around the Telemetry::FieldCollection::AddField method.
* @param type Type of the field to add.
@@ -39,7 +64,6 @@ public:
private:
Telemetry::FieldCollection field_collection; ///< Tracks all added fields for the session
- std::unique_ptr<Telemetry::VisitorInterface> backend; ///< Backend interface that logs fields
};
/**
diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt
index 1c7db28c0..5b4e032bd 100644
--- a/src/input_common/CMakeLists.txt
+++ b/src/input_common/CMakeLists.txt
@@ -7,15 +7,18 @@ add_library(input_common STATIC
main.h
motion_emu.cpp
motion_emu.h
-
- $<$<BOOL:${SDL2_FOUND}>:sdl/sdl.cpp sdl/sdl.h>
+ sdl/sdl.cpp
+ sdl/sdl.h
)
-create_target_directory_groups(input_common)
-
-target_link_libraries(input_common PUBLIC core PRIVATE common)
-
if(SDL2_FOUND)
+ target_sources(input_common PRIVATE
+ sdl/sdl_impl.cpp
+ sdl/sdl_impl.h
+ )
target_link_libraries(input_common PRIVATE SDL2)
target_compile_definitions(input_common PRIVATE HAVE_SDL2)
endif()
+
+create_target_directory_groups(input_common)
+target_link_libraries(input_common PUBLIC core PRIVATE common)
diff --git a/src/input_common/keyboard.cpp b/src/input_common/keyboard.cpp
index 525fe6abc..078374be5 100644
--- a/src/input_common/keyboard.cpp
+++ b/src/input_common/keyboard.cpp
@@ -36,18 +36,18 @@ struct KeyButtonPair {
class KeyButtonList {
public:
void AddKeyButton(int key_code, KeyButton* key_button) {
- std::lock_guard<std::mutex> guard(mutex);
+ std::lock_guard guard{mutex};
list.push_back(KeyButtonPair{key_code, key_button});
}
void RemoveKeyButton(const KeyButton* key_button) {
- std::lock_guard<std::mutex> guard(mutex);
+ std::lock_guard guard{mutex};
list.remove_if(
[key_button](const KeyButtonPair& pair) { return pair.key_button == key_button; });
}
void ChangeKeyStatus(int key_code, bool pressed) {
- std::lock_guard<std::mutex> guard(mutex);
+ std::lock_guard guard{mutex};
for (const KeyButtonPair& pair : list) {
if (pair.key_code == key_code)
pair.key_button->status.store(pressed);
@@ -55,7 +55,7 @@ public:
}
void ChangeAllKeyStatus(bool pressed) {
- std::lock_guard<std::mutex> guard(mutex);
+ std::lock_guard guard{mutex};
for (const KeyButtonPair& pair : list) {
pair.key_button->status.store(pressed);
}
diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp
index 37f572853..8e66c1b15 100644
--- a/src/input_common/main.cpp
+++ b/src/input_common/main.cpp
@@ -17,10 +17,7 @@ namespace InputCommon {
static std::shared_ptr<Keyboard> keyboard;
static std::shared_ptr<MotionEmu> motion_emu;
-
-#ifdef HAVE_SDL2
-static std::thread poll_thread;
-#endif
+static std::unique_ptr<SDL::State> sdl;
void Init() {
keyboard = std::make_shared<Keyboard>();
@@ -30,15 +27,7 @@ void Init() {
motion_emu = std::make_shared<MotionEmu>();
Input::RegisterFactory<Input::MotionDevice>("motion_emu", motion_emu);
-#ifdef HAVE_SDL2
- SDL::Init();
-#endif
-}
-
-void StartJoystickEventHandler() {
-#ifdef HAVE_SDL2
- poll_thread = std::thread(SDL::PollLoop);
-#endif
+ sdl = SDL::Init();
}
void Shutdown() {
@@ -47,11 +36,7 @@ void Shutdown() {
Input::UnregisterFactory<Input::AnalogDevice>("analog_from_button");
Input::UnregisterFactory<Input::MotionDevice>("motion_emu");
motion_emu.reset();
-
-#ifdef HAVE_SDL2
- SDL::Shutdown();
- poll_thread.join();
-#endif
+ sdl.reset();
}
Keyboard* GetKeyboard() {
@@ -88,7 +73,7 @@ namespace Polling {
std::vector<std::unique_ptr<DevicePoller>> GetPollers(DeviceType type) {
#ifdef HAVE_SDL2
- return SDL::Polling::GetPollers(type);
+ return sdl->GetPollers(type);
#else
return {};
#endif
diff --git a/src/input_common/main.h b/src/input_common/main.h
index 9eb13106e..77a0ce90b 100644
--- a/src/input_common/main.h
+++ b/src/input_common/main.h
@@ -20,8 +20,6 @@ void Init();
/// Deregisters all built-in input device factories and shuts them down.
void Shutdown();
-void StartJoystickEventHandler();
-
class Keyboard;
/// Gets the keyboard button device factory.
diff --git a/src/input_common/motion_emu.cpp b/src/input_common/motion_emu.cpp
index 6d96d4019..868251628 100644
--- a/src/input_common/motion_emu.cpp
+++ b/src/input_common/motion_emu.cpp
@@ -39,7 +39,7 @@ public:
void Tilt(int x, int y) {
auto mouse_move = Common::MakeVec(x, y) - mouse_origin;
if (is_tilting) {
- std::lock_guard<std::mutex> guard(tilt_mutex);
+ std::lock_guard guard{tilt_mutex};
if (mouse_move.x == 0 && mouse_move.y == 0) {
tilt_angle = 0;
} else {
@@ -51,13 +51,13 @@ public:
}
void EndTilt() {
- std::lock_guard<std::mutex> guard(tilt_mutex);
+ std::lock_guard guard{tilt_mutex};
tilt_angle = 0;
is_tilting = false;
}
std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetStatus() {
- std::lock_guard<std::mutex> guard(status_mutex);
+ std::lock_guard guard{status_mutex};
return status;
}
@@ -93,7 +93,7 @@ private:
old_q = q;
{
- std::lock_guard<std::mutex> guard(tilt_mutex);
+ std::lock_guard guard{tilt_mutex};
// Find the quaternion describing current 3DS tilting
q = Common::MakeQuaternion(
@@ -115,7 +115,7 @@ private:
// Update the sensor state
{
- std::lock_guard<std::mutex> guard(status_mutex);
+ std::lock_guard guard{status_mutex};
status = std::make_tuple(gravity, angular_rate);
}
}
diff --git a/src/input_common/sdl/sdl.cpp b/src/input_common/sdl/sdl.cpp
index faf3c1fa3..644db3448 100644
--- a/src/input_common/sdl/sdl.cpp
+++ b/src/input_common/sdl/sdl.cpp
@@ -1,631 +1,19 @@
-// Copyright 2017 Citra Emulator Project
+// Copyright 2018 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include <algorithm>
-#include <atomic>
-#include <cmath>
-#include <functional>
-#include <iterator>
-#include <mutex>
-#include <string>
-#include <thread>
-#include <tuple>
-#include <unordered_map>
-#include <utility>
-#include <vector>
-#include <SDL.h>
-#include "common/assert.h"
-#include "common/logging/log.h"
-#include "common/math_util.h"
-#include "common/param_package.h"
-#include "common/threadsafe_queue.h"
-#include "input_common/main.h"
#include "input_common/sdl/sdl.h"
+#ifdef HAVE_SDL2
+#include "input_common/sdl/sdl_impl.h"
+#endif
-namespace InputCommon {
+namespace InputCommon::SDL {
-namespace SDL {
-
-class SDLJoystick;
-class SDLButtonFactory;
-class SDLAnalogFactory;
-
-/// Map of GUID of a list of corresponding virtual Joysticks
-static std::unordered_map<std::string, std::vector<std::shared_ptr<SDLJoystick>>> joystick_map;
-static std::mutex joystick_map_mutex;
-
-static std::shared_ptr<SDLButtonFactory> button_factory;
-static std::shared_ptr<SDLAnalogFactory> analog_factory;
-
-/// Used by the Pollers during config
-static std::atomic<bool> polling;
-static Common::SPSCQueue<SDL_Event> event_queue;
-
-static std::atomic<bool> initialized = false;
-
-static std::string GetGUID(SDL_Joystick* joystick) {
- SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick);
- char guid_str[33];
- SDL_JoystickGetGUIDString(guid, guid_str, sizeof(guid_str));
- return guid_str;
-}
-
-class SDLJoystick {
-public:
- SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick,
- decltype(&SDL_JoystickClose) deleter = &SDL_JoystickClose)
- : guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, deleter} {}
-
- void SetButton(int button, bool value) {
- std::lock_guard<std::mutex> lock(mutex);
- state.buttons[button] = value;
- }
-
- bool GetButton(int button) const {
- std::lock_guard<std::mutex> lock(mutex);
- return state.buttons.at(button);
- }
-
- void SetAxis(int axis, Sint16 value) {
- std::lock_guard<std::mutex> lock(mutex);
- state.axes[axis] = value;
- }
-
- float GetAxis(int axis) const {
- std::lock_guard<std::mutex> lock(mutex);
- return state.axes.at(axis) / 32767.0f;
- }
-
- std::tuple<float, float> GetAnalog(int axis_x, int axis_y) const {
- float x = GetAxis(axis_x);
- float y = GetAxis(axis_y);
- y = -y; // 3DS uses an y-axis inverse from SDL
-
- // 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 std::make_tuple(x, y);
- }
-
- void SetHat(int hat, Uint8 direction) {
- std::lock_guard<std::mutex> lock(mutex);
- state.hats[hat] = direction;
- }
-
- bool GetHatDirection(int hat, Uint8 direction) const {
- std::lock_guard<std::mutex> lock(mutex);
- return (state.hats.at(hat) & direction) != 0;
- }
- /**
- * The guid of the joystick
- */
- const std::string& GetGUID() const {
- return guid;
- }
-
- /**
- * The number of joystick from the same type that were connected before this joystick
- */
- int GetPort() const {
- return port;
- }
-
- SDL_Joystick* GetSDLJoystick() const {
- return sdl_joystick.get();
- }
-
- void SetSDLJoystick(SDL_Joystick* joystick,
- decltype(&SDL_JoystickClose) deleter = &SDL_JoystickClose) {
- sdl_joystick =
- std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)>(joystick, deleter);
- }
-
-private:
- struct State {
- std::unordered_map<int, bool> buttons;
- std::unordered_map<int, Sint16> axes;
- std::unordered_map<int, Uint8> hats;
- } state;
- std::string guid;
- int port;
- std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> sdl_joystick;
- mutable std::mutex mutex;
-};
-
-/**
- * Get the nth joystick with the corresponding GUID
- */
-static std::shared_ptr<SDLJoystick> GetSDLJoystickByGUID(const std::string& guid, int port) {
- std::lock_guard<std::mutex> lock(joystick_map_mutex);
- const auto it = joystick_map.find(guid);
- if (it != joystick_map.end()) {
- while (it->second.size() <= port) {
- auto joystick = std::make_shared<SDLJoystick>(guid, it->second.size(), nullptr,
- [](SDL_Joystick*) {});
- it->second.emplace_back(std::move(joystick));
- }
- return it->second[port];
- }
- auto joystick = std::make_shared<SDLJoystick>(guid, 0, nullptr, [](SDL_Joystick*) {});
- return joystick_map[guid].emplace_back(std::move(joystick));
-}
-
-/**
- * Check how many identical joysticks (by guid) were connected before the one with sdl_id and so tie
- * it to a SDLJoystick with the same guid and that port
- */
-static std::shared_ptr<SDLJoystick> GetSDLJoystickBySDLID(SDL_JoystickID sdl_id) {
- std::lock_guard<std::mutex> lock(joystick_map_mutex);
- auto sdl_joystick = SDL_JoystickFromInstanceID(sdl_id);
- const std::string guid = GetGUID(sdl_joystick);
- auto map_it = joystick_map.find(guid);
- if (map_it != joystick_map.end()) {
- 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...
- 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);
- return *nullptr_it;
- }
- // There is no SDLJoystick without a mapped SDL_Joystick
- // Create a new SDLJoystick
- auto joystick = std::make_shared<SDLJoystick>(guid, map_it->second.size(), sdl_joystick);
- return map_it->second.emplace_back(std::move(joystick));
- }
- auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick);
- return joystick_map[guid].emplace_back(std::move(joystick));
-}
-
-void InitJoystick(int joystick_index) {
- std::lock_guard<std::mutex> lock(joystick_map_mutex);
- SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index);
- if (!sdl_joystick) {
- LOG_ERROR(Input, "failed to open joystick {}", joystick_index);
- return;
- }
- std::string guid = GetGUID(sdl_joystick);
- if (joystick_map.find(guid) == joystick_map.end()) {
- auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick);
- 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);
- return;
- }
- auto joystick = std::make_shared<SDLJoystick>(guid, joystick_guid_list.size(), sdl_joystick);
- joystick_guid_list.emplace_back(std::move(joystick));
-}
-
-void CloseJoystick(SDL_Joystick* sdl_joystick) {
- std::lock_guard<std::mutex> lock(joystick_map_mutex);
- std::string guid = GetGUID(sdl_joystick);
- // This call to guid is save since the joystick is guranteed to be in that map
- 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_it)->SetSDLJoystick(nullptr, [](SDL_Joystick*) {});
-}
-
-void HandleGameControllerEvent(const SDL_Event& event) {
- switch (event.type) {
- case SDL_JOYBUTTONUP: {
- auto joystick = GetSDLJoystickBySDLID(event.jbutton.which);
- if (joystick) {
- joystick->SetButton(event.jbutton.button, false);
- }
- break;
- }
- case SDL_JOYBUTTONDOWN: {
- auto joystick = GetSDLJoystickBySDLID(event.jbutton.which);
- if (joystick) {
- joystick->SetButton(event.jbutton.button, true);
- }
- break;
- }
- case SDL_JOYHATMOTION: {
- auto joystick = GetSDLJoystickBySDLID(event.jhat.which);
- if (joystick) {
- joystick->SetHat(event.jhat.hat, event.jhat.value);
- }
- break;
- }
- case SDL_JOYAXISMOTION: {
- auto joystick = GetSDLJoystickBySDLID(event.jaxis.which);
- if (joystick) {
- joystick->SetAxis(event.jaxis.axis, event.jaxis.value);
- }
- break;
- }
- case SDL_JOYDEVICEREMOVED:
- LOG_DEBUG(Input, "Controller removed with Instance_ID {}", event.jdevice.which);
- CloseJoystick(SDL_JoystickFromInstanceID(event.jdevice.which));
- break;
- case SDL_JOYDEVICEADDED:
- LOG_DEBUG(Input, "Controller connected with device index {}", event.jdevice.which);
- InitJoystick(event.jdevice.which);
- break;
- }
-}
-
-void CloseSDLJoysticks() {
- std::lock_guard<std::mutex> lock(joystick_map_mutex);
- joystick_map.clear();
-}
-
-void PollLoop() {
- if (SDL_Init(SDL_INIT_JOYSTICK) < 0) {
- LOG_CRITICAL(Input, "SDL_Init(SDL_INIT_JOYSTICK) failed with: {}", SDL_GetError());
- return;
- }
-
- SDL_Event event;
- while (initialized) {
- // Wait for 10 ms or until an event happens
- if (SDL_WaitEventTimeout(&event, 10)) {
- // Don't handle the event if we are configuring
- if (polling) {
- event_queue.Push(event);
- } else {
- HandleGameControllerEvent(event);
- }
- }
- }
- CloseSDLJoysticks();
- SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
-}
-
-class SDLButton final : public Input::ButtonDevice {
-public:
- explicit SDLButton(std::shared_ptr<SDLJoystick> joystick_, int button_)
- : joystick(std::move(joystick_)), button(button_) {}
-
- bool GetStatus() const override {
- return joystick->GetButton(button);
- }
-
-private:
- std::shared_ptr<SDLJoystick> joystick;
- int button;
-};
-
-class SDLDirectionButton final : public Input::ButtonDevice {
-public:
- explicit SDLDirectionButton(std::shared_ptr<SDLJoystick> joystick_, int hat_, Uint8 direction_)
- : joystick(std::move(joystick_)), hat(hat_), direction(direction_) {}
-
- bool GetStatus() const override {
- return joystick->GetHatDirection(hat, direction);
- }
-
-private:
- std::shared_ptr<SDLJoystick> joystick;
- int hat;
- Uint8 direction;
-};
-
-class SDLAxisButton final : public Input::ButtonDevice {
-public:
- explicit SDLAxisButton(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_) {}
-
- bool GetStatus() const override {
- float axis_value = joystick->GetAxis(axis);
- if (trigger_if_greater)
- return axis_value > threshold;
- return axis_value < threshold;
- }
-
-private:
- std::shared_ptr<SDLJoystick> joystick;
- int axis;
- float threshold;
- bool trigger_if_greater;
-};
-
-class SDLAnalog final : public Input::AnalogDevice {
-public:
- SDLAnalog(std::shared_ptr<SDLJoystick> joystick_, int axis_x_, int axis_y_)
- : joystick(std::move(joystick_)), axis_x(axis_x_), axis_y(axis_y_) {}
-
- std::tuple<float, float> GetStatus() const override {
- return joystick->GetAnalog(axis_x, axis_y);
- }
-
-private:
- std::shared_ptr<SDLJoystick> joystick;
- int axis_x;
- int axis_y;
-};
-
-/// A button device factory that creates button devices from SDL joystick
-class SDLButtonFactory final : public Input::Factory<Input::ButtonDevice> {
-public:
- /**
- * Creates a button device from a joystick button
- * @param params contains parameters for creating the device:
- * - "guid": the guid of the joystick to bind
- * - "port": the nth joystick of the same type to bind
- * - "button"(optional): the index of the button to bind
- * - "hat"(optional): the index of the hat to bind as direction buttons
- * - "axis"(optional): the index of the axis to bind
- * - "direction"(only used for hat): the direction name of the hat to bind. Can be "up",
- * "down", "left" or "right"
- * - "threshold"(only used for axis): a float value in (-1.0, 1.0) which the button is
- * triggered if the axis value crosses
- * - "direction"(only used for axis): "+" means the button is triggered when the axis
- * value is greater than the threshold; "-" means the button is triggered when the axis
- * value is smaller than the threshold
- */
- std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override {
- const std::string guid = params.Get("guid", "0");
- const int port = params.Get("port", 0);
-
- auto joystick = 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<SDLDirectionButton>(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<SDLAxisButton>(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<SDLButton>(joystick, button);
- }
-};
-
-/// An analog device factory that creates analog devices from SDL joystick
-class SDLAnalogFactory final : public Input::Factory<Input::AnalogDevice> {
-public:
- /**
- * Creates 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
- * - "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> Create(const Common::ParamPackage& params) override {
- const std::string guid = params.Get("guid", "0");
- 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);
-
- auto joystick = 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);
- }
-};
-
-void Init() {
- using namespace Input;
- RegisterFactory<ButtonDevice>("sdl", std::make_shared<SDLButtonFactory>());
- RegisterFactory<AnalogDevice>("sdl", std::make_shared<SDLAnalogFactory>());
- polling = false;
- initialized = true;
-}
-
-void Shutdown() {
- if (initialized) {
- using namespace Input;
- UnregisterFactory<ButtonDevice>("sdl");
- UnregisterFactory<AnalogDevice>("sdl");
- initialized = false;
- }
-}
-
-Common::ParamPackage SDLEventToButtonParamPackage(const SDL_Event& event) {
- Common::ParamPackage params({{"engine", "sdl"}});
- switch (event.type) {
- case SDL_JOYAXISMOTION: {
- auto joystick = GetSDLJoystickBySDLID(event.jaxis.which);
- params.Set("port", joystick->GetPort());
- params.Set("guid", joystick->GetGUID());
- params.Set("axis", event.jaxis.axis);
- if (event.jaxis.value > 0) {
- params.Set("direction", "+");
- params.Set("threshold", "0.5");
- } else {
- params.Set("direction", "-");
- params.Set("threshold", "-0.5");
- }
- break;
- }
- case SDL_JOYBUTTONUP: {
- auto joystick = GetSDLJoystickBySDLID(event.jbutton.which);
- params.Set("port", joystick->GetPort());
- params.Set("guid", joystick->GetGUID());
- params.Set("button", event.jbutton.button);
- break;
- }
- case SDL_JOYHATMOTION: {
- auto joystick = GetSDLJoystickBySDLID(event.jhat.which);
- params.Set("port", joystick->GetPort());
- params.Set("guid", joystick->GetGUID());
- params.Set("hat", event.jhat.hat);
- switch (event.jhat.value) {
- case SDL_HAT_UP:
- params.Set("direction", "up");
- break;
- case SDL_HAT_DOWN:
- params.Set("direction", "down");
- break;
- case SDL_HAT_LEFT:
- params.Set("direction", "left");
- break;
- case SDL_HAT_RIGHT:
- params.Set("direction", "right");
- break;
- default:
- return {};
- }
- break;
- }
- }
- return params;
-}
-
-namespace Polling {
-
-class SDLPoller : public InputCommon::Polling::DevicePoller {
-public:
- void Start() override {
- event_queue.Clear();
- polling = true;
- }
-
- void Stop() override {
- polling = false;
- }
-};
-
-class SDLButtonPoller final : public SDLPoller {
-public:
- Common::ParamPackage GetNextInput() override {
- SDL_Event event;
- while (event_queue.Pop(event)) {
- switch (event.type) {
- case SDL_JOYAXISMOTION:
- if (std::abs(event.jaxis.value / 32767.0) < 0.5) {
- break;
- }
- case SDL_JOYBUTTONUP:
- case SDL_JOYHATMOTION:
- return SDLEventToButtonParamPackage(event);
- }
- }
- return {};
- }
-};
-
-class SDLAnalogPoller final : public SDLPoller {
-public:
- void Start() override {
- SDLPoller::Start();
-
- // Reset stored axes
- analog_xaxis = -1;
- analog_yaxis = -1;
- analog_axes_joystick = -1;
- }
-
- Common::ParamPackage GetNextInput() override {
- SDL_Event event;
- while (event_queue.Pop(event)) {
- if (event.type != SDL_JOYAXISMOTION || std::abs(event.jaxis.value / 32767.0) < 0.5) {
- continue;
- }
- // An analog device needs two axes, so we need to store the axis for later and wait for
- // a second SDL event. The axes also must be from the same joystick.
- int axis = event.jaxis.axis;
- if (analog_xaxis == -1) {
- analog_xaxis = axis;
- analog_axes_joystick = event.jaxis.which;
- } else if (analog_yaxis == -1 && analog_xaxis != axis &&
- analog_axes_joystick == event.jaxis.which) {
- analog_yaxis = axis;
- }
- }
- Common::ParamPackage params;
- if (analog_xaxis != -1 && analog_yaxis != -1) {
- auto joystick = GetSDLJoystickBySDLID(event.jaxis.which);
- params.Set("engine", "sdl");
- params.Set("port", joystick->GetPort());
- params.Set("guid", joystick->GetGUID());
- params.Set("axis_x", analog_xaxis);
- params.Set("axis_y", analog_yaxis);
- analog_xaxis = -1;
- analog_yaxis = -1;
- analog_axes_joystick = -1;
- return params;
- }
- return params;
- }
-
-private:
- int analog_xaxis = -1;
- int analog_yaxis = -1;
- SDL_JoystickID analog_axes_joystick = -1;
-};
-
-std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> GetPollers(
- InputCommon::Polling::DeviceType type) {
- std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> pollers;
- switch (type) {
- case InputCommon::Polling::DeviceType::Analog:
- pollers.push_back(std::make_unique<SDLAnalogPoller>());
- break;
- case InputCommon::Polling::DeviceType::Button:
- pollers.push_back(std::make_unique<SDLButtonPoller>());
- break;
- }
- return pollers;
+std::unique_ptr<State> Init() {
+#ifdef HAVE_SDL2
+ return std::make_unique<SDLState>();
+#else
+ return std::make_unique<NullState>();
+#endif
}
-} // namespace Polling
-} // namespace SDL
-} // namespace InputCommon
+} // namespace InputCommon::SDL
diff --git a/src/input_common/sdl/sdl.h b/src/input_common/sdl/sdl.h
index 0206860d3..5306daa70 100644
--- a/src/input_common/sdl/sdl.h
+++ b/src/input_common/sdl/sdl.h
@@ -1,4 +1,4 @@
-// Copyright 2017 Citra Emulator Project
+// Copyright 2018 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -6,46 +6,32 @@
#include <memory>
#include <vector>
-#include "core/frontend/input.h"
-
-union SDL_Event;
-namespace Common {
-class ParamPackage;
-}
-namespace InputCommon {
-namespace Polling {
+#include "input_common/main.h"
+
+namespace InputCommon::Polling {
class DevicePoller;
enum class DeviceType;
-} // namespace Polling
-} // namespace InputCommon
-
-namespace InputCommon {
-namespace SDL {
-
-/// Initializes and registers SDL device factories
-void Init();
-
-/// Unresisters SDL device factories and shut them down.
-void Shutdown();
+} // namespace InputCommon::Polling
-/// Needs to be called before SDL_QuitSubSystem.
-void CloseSDLJoysticks();
+namespace InputCommon::SDL {
-/// Handle SDL_Events for joysticks from SDL_PollEvent
-void HandleGameControllerEvent(const SDL_Event& event);
+class State {
+public:
+ using Pollers = std::vector<std::unique_ptr<Polling::DevicePoller>>;
-/// A Loop that calls HandleGameControllerEvent until Shutdown is called
-void PollLoop();
+ /// Unregisters SDL device factories and shut them down.
+ virtual ~State() = default;
-/// Creates a ParamPackage from an SDL_Event that can directly be used to create a ButtonDevice
-Common::ParamPackage SDLEventToButtonParamPackage(const SDL_Event& event);
+ virtual Pollers GetPollers(Polling::DeviceType type) = 0;
+};
-namespace Polling {
+class NullState : public State {
+public:
+ Pollers GetPollers(Polling::DeviceType type) override {
+ return {};
+ }
+};
-/// Get all DevicePoller that use the SDL backend for a specific device type
-std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> GetPollers(
- InputCommon::Polling::DeviceType type);
+std::unique_ptr<State> Init();
-} // namespace Polling
-} // namespace SDL
-} // namespace InputCommon
+} // namespace InputCommon::SDL
diff --git a/src/input_common/sdl/sdl_impl.cpp b/src/input_common/sdl/sdl_impl.cpp
new file mode 100644
index 000000000..d2e9d278f
--- /dev/null
+++ b/src/input_common/sdl/sdl_impl.cpp
@@ -0,0 +1,667 @@
+// Copyright 2018 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <atomic>
+#include <cmath>
+#include <functional>
+#include <mutex>
+#include <string>
+#include <thread>
+#include <tuple>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+#include <SDL.h>
+#include "common/logging/log.h"
+#include "common/math_util.h"
+#include "common/param_package.h"
+#include "common/threadsafe_queue.h"
+#include "core/frontend/input.h"
+#include "input_common/sdl/sdl_impl.h"
+
+namespace InputCommon::SDL {
+
+static std::string GetGUID(SDL_Joystick* joystick) {
+ const SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick);
+ char guid_str[33];
+ SDL_JoystickGetGUIDString(guid, guid_str, sizeof(guid_str));
+ return guid_str;
+}
+
+/// Creates a ParamPackage from an SDL_Event that can directly be used to create a ButtonDevice
+static Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event);
+
+static int SDLEventWatcher(void* user_data, SDL_Event* event) {
+ auto* const sdl_state = static_cast<SDLState*>(user_data);
+
+ // Don't handle the event if we are configuring
+ if (sdl_state->polling) {
+ sdl_state->event_queue.Push(*event);
+ } else {
+ sdl_state->HandleGameControllerEvent(*event);
+ }
+
+ return 0;
+}
+
+class SDLJoystick {
+public:
+ SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick)
+ : guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, &SDL_JoystickClose} {}
+
+ void SetButton(int button, bool value) {
+ std::lock_guard lock{mutex};
+ state.buttons.insert_or_assign(button, value);
+ }
+
+ bool GetButton(int button) const {
+ std::lock_guard lock{mutex};
+ return state.buttons.at(button);
+ }
+
+ void SetAxis(int axis, Sint16 value) {
+ std::lock_guard lock{mutex};
+ state.axes.insert_or_assign(axis, value);
+ }
+
+ float GetAxis(int axis) const {
+ std::lock_guard lock{mutex};
+ return state.axes.at(axis) / 32767.0f;
+ }
+
+ std::tuple<float, float> GetAnalog(int axis_x, int axis_y) const {
+ float x = GetAxis(axis_x);
+ float y = GetAxis(axis_y);
+ y = -y; // 3DS uses an y-axis inverse from SDL
+
+ // 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 std::make_tuple(x, y);
+ }
+
+ void SetHat(int hat, Uint8 direction) {
+ std::lock_guard lock{mutex};
+ state.hats.insert_or_assign(hat, direction);
+ }
+
+ bool GetHatDirection(int hat, Uint8 direction) const {
+ std::lock_guard lock{mutex};
+ return (state.hats.at(hat) & direction) != 0;
+ }
+ /**
+ * The guid of the joystick
+ */
+ const std::string& GetGUID() const {
+ return guid;
+ }
+
+ /**
+ * The number of joystick from the same type that were connected before this joystick
+ */
+ int GetPort() const {
+ return port;
+ }
+
+ SDL_Joystick* GetSDLJoystick() const {
+ return sdl_joystick.get();
+ }
+
+ void SetSDLJoystick(SDL_Joystick* joystick) {
+ sdl_joystick.reset(joystick);
+ }
+
+private:
+ struct State {
+ std::unordered_map<int, bool> buttons;
+ std::unordered_map<int, Sint16> axes;
+ std::unordered_map<int, Uint8> hats;
+ } state;
+ std::string guid;
+ int port;
+ std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> sdl_joystick;
+ mutable std::mutex mutex;
+};
+
+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);
+ it->second.emplace_back(std::move(joystick));
+ }
+ return it->second[port];
+ }
+ auto joystick = std::make_shared<SDLJoystick>(guid, 0, 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);
+ 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);
+ 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);
+ return map_it->second.emplace_back(std::move(joystick));
+ }
+
+ auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick);
+ return joystick_map[guid].emplace_back(std::move(joystick));
+}
+
+void SDLState::InitJoystick(int joystick_index) {
+ SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index);
+ if (!sdl_joystick) {
+ LOG_ERROR(Input, "failed to open joystick {}", joystick_index);
+ return;
+ }
+ const std::string guid = GetGUID(sdl_joystick);
+
+ std::lock_guard lock{joystick_map_mutex};
+ if (joystick_map.find(guid) == joystick_map.end()) {
+ auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick);
+ 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);
+ return;
+ }
+ const int port = static_cast<int>(joystick_guid_list.size());
+ auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick);
+ joystick_guid_list.emplace_back(std::move(joystick));
+}
+
+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);
+}
+
+void SDLState::HandleGameControllerEvent(const SDL_Event& event) {
+ switch (event.type) {
+ case SDL_JOYBUTTONUP: {
+ if (auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) {
+ joystick->SetButton(event.jbutton.button, false);
+ }
+ break;
+ }
+ case SDL_JOYBUTTONDOWN: {
+ if (auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) {
+ joystick->SetButton(event.jbutton.button, true);
+ }
+ break;
+ }
+ case SDL_JOYHATMOTION: {
+ if (auto joystick = GetSDLJoystickBySDLID(event.jhat.which)) {
+ joystick->SetHat(event.jhat.hat, event.jhat.value);
+ }
+ break;
+ }
+ case SDL_JOYAXISMOTION: {
+ if (auto joystick = GetSDLJoystickBySDLID(event.jaxis.which)) {
+ joystick->SetAxis(event.jaxis.axis, event.jaxis.value);
+ }
+ break;
+ }
+ case SDL_JOYDEVICEREMOVED:
+ LOG_DEBUG(Input, "Controller removed with Instance_ID {}", event.jdevice.which);
+ CloseJoystick(SDL_JoystickFromInstanceID(event.jdevice.which));
+ break;
+ case SDL_JOYDEVICEADDED:
+ LOG_DEBUG(Input, "Controller connected with device index {}", event.jdevice.which);
+ InitJoystick(event.jdevice.which);
+ break;
+ }
+}
+
+void SDLState::CloseJoysticks() {
+ std::lock_guard lock{joystick_map_mutex};
+ joystick_map.clear();
+}
+
+class SDLButton final : public Input::ButtonDevice {
+public:
+ explicit SDLButton(std::shared_ptr<SDLJoystick> joystick_, int button_)
+ : joystick(std::move(joystick_)), button(button_) {}
+
+ bool GetStatus() const override {
+ return joystick->GetButton(button);
+ }
+
+private:
+ std::shared_ptr<SDLJoystick> joystick;
+ int button;
+};
+
+class SDLDirectionButton final : public Input::ButtonDevice {
+public:
+ explicit SDLDirectionButton(std::shared_ptr<SDLJoystick> joystick_, int hat_, Uint8 direction_)
+ : joystick(std::move(joystick_)), hat(hat_), direction(direction_) {}
+
+ bool GetStatus() const override {
+ return joystick->GetHatDirection(hat, direction);
+ }
+
+private:
+ std::shared_ptr<SDLJoystick> joystick;
+ int hat;
+ Uint8 direction;
+};
+
+class SDLAxisButton final : public Input::ButtonDevice {
+public:
+ explicit SDLAxisButton(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_) {}
+
+ bool GetStatus() const override {
+ const float axis_value = joystick->GetAxis(axis);
+ if (trigger_if_greater) {
+ return axis_value > threshold;
+ }
+ return axis_value < threshold;
+ }
+
+private:
+ std::shared_ptr<SDLJoystick> joystick;
+ int axis;
+ float threshold;
+ bool trigger_if_greater;
+};
+
+class SDLAnalog final : public Input::AnalogDevice {
+public:
+ SDLAnalog(std::shared_ptr<SDLJoystick> joystick_, int axis_x_, int axis_y_, float deadzone_)
+ : joystick(std::move(joystick_)), axis_x(axis_x_), axis_y(axis_y_), deadzone(deadzone_) {}
+
+ std::tuple<float, float> GetStatus() const override {
+ const auto [x, y] = joystick->GetAnalog(axis_x, axis_y);
+ const float r = std::sqrt((x * x) + (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);
+ }
+
+private:
+ std::shared_ptr<SDLJoystick> joystick;
+ const int axis_x;
+ const int axis_y;
+ const float deadzone;
+};
+
+/// A button device factory that creates button devices from SDL joystick
+class SDLButtonFactory final : public Input::Factory<Input::ButtonDevice> {
+public:
+ explicit SDLButtonFactory(SDLState& state_) : state(state_) {}
+
+ /**
+ * Creates a button device from a joystick button
+ * @param params contains parameters for creating the device:
+ * - "guid": the guid of the joystick to bind
+ * - "port": the nth joystick of the same type to bind
+ * - "button"(optional): the index of the button to bind
+ * - "hat"(optional): the index of the hat to bind as direction buttons
+ * - "axis"(optional): the index of the axis to bind
+ * - "direction"(only used for hat): the direction name of the hat to bind. Can be "up",
+ * "down", "left" or "right"
+ * - "threshold"(only used for axis): a float value in (-1.0, 1.0) which the button is
+ * triggered if the axis value crosses
+ * - "direction"(only used for axis): "+" means the button is triggered when the axis
+ * value is greater than the threshold; "-" means the button is triggered when the axis
+ * value is smaller than the threshold
+ */
+ std::unique_ptr<Input::ButtonDevice> 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<SDLDirectionButton>(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<SDLAxisButton>(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<SDLButton>(joystick, button);
+ }
+
+private:
+ SDLState& state;
+};
+
+/// An analog device factory that creates analog devices from SDL joystick
+class SDLAnalogFactory final : public Input::Factory<Input::AnalogDevice> {
+public:
+ explicit SDLAnalogFactory(SDLState& state_) : state(state_) {}
+ /**
+ * Creates 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
+ * - "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> Create(const Common::ParamPackage& params) override {
+ const std::string guid = params.Get("guid", "0");
+ 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, .99f);
+
+ 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);
+ }
+
+private:
+ SDLState& state;
+};
+
+SDLState::SDLState() {
+ using namespace Input;
+ RegisterFactory<ButtonDevice>("sdl", std::make_shared<SDLButtonFactory>(*this));
+ RegisterFactory<AnalogDevice>("sdl", std::make_shared<SDLAnalogFactory>(*this));
+
+ // If the frontend is going to manage the event loop, then we dont start one here
+ start_thread = !SDL_WasInit(SDL_INIT_JOYSTICK);
+ if (start_thread && SDL_Init(SDL_INIT_JOYSTICK) < 0) {
+ LOG_CRITICAL(Input, "SDL_Init(SDL_INIT_JOYSTICK) failed with: {}", SDL_GetError());
+ return;
+ }
+ 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());
+ }
+
+ SDL_AddEventWatch(&SDLEventWatcher, this);
+
+ initialized = true;
+ if (start_thread) {
+ poll_thread = std::thread([this] {
+ using namespace std::chrono_literals;
+ while (initialized) {
+ SDL_PumpEvents();
+ std::this_thread::sleep_for(10ms);
+ }
+ });
+ }
+ // Because the events for joystick connection happens before we have our event watcher added, we
+ // can just open all the joysticks right here
+ for (int i = 0; i < SDL_NumJoysticks(); ++i) {
+ InitJoystick(i);
+ }
+}
+
+SDLState::~SDLState() {
+ using namespace Input;
+ UnregisterFactory<ButtonDevice>("sdl");
+ UnregisterFactory<AnalogDevice>("sdl");
+
+ CloseJoysticks();
+ SDL_DelEventWatch(&SDLEventWatcher, this);
+
+ initialized = false;
+ if (start_thread) {
+ poll_thread.join();
+ SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
+ }
+}
+
+static Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event) {
+ Common::ParamPackage params({{"engine", "sdl"}});
+
+ switch (event.type) {
+ case SDL_JOYAXISMOTION: {
+ const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which);
+ params.Set("port", joystick->GetPort());
+ params.Set("guid", joystick->GetGUID());
+ params.Set("axis", event.jaxis.axis);
+ if (event.jaxis.value > 0) {
+ params.Set("direction", "+");
+ params.Set("threshold", "0.5");
+ } else {
+ params.Set("direction", "-");
+ params.Set("threshold", "-0.5");
+ }
+ break;
+ }
+ case SDL_JOYBUTTONUP: {
+ const auto joystick = state.GetSDLJoystickBySDLID(event.jbutton.which);
+ params.Set("port", joystick->GetPort());
+ params.Set("guid", joystick->GetGUID());
+ params.Set("button", event.jbutton.button);
+ break;
+ }
+ case SDL_JOYHATMOTION: {
+ const auto joystick = state.GetSDLJoystickBySDLID(event.jhat.which);
+ params.Set("port", joystick->GetPort());
+ params.Set("guid", joystick->GetGUID());
+ params.Set("hat", event.jhat.hat);
+ switch (event.jhat.value) {
+ case SDL_HAT_UP:
+ params.Set("direction", "up");
+ break;
+ case SDL_HAT_DOWN:
+ params.Set("direction", "down");
+ break;
+ case SDL_HAT_LEFT:
+ params.Set("direction", "left");
+ break;
+ case SDL_HAT_RIGHT:
+ params.Set("direction", "right");
+ break;
+ default:
+ return {};
+ }
+ break;
+ }
+ }
+ return params;
+}
+
+namespace Polling {
+
+class SDLPoller : public InputCommon::Polling::DevicePoller {
+public:
+ explicit SDLPoller(SDLState& state_) : state(state_) {}
+
+ void Start() override {
+ state.event_queue.Clear();
+ state.polling = true;
+ }
+
+ void Stop() override {
+ state.polling = false;
+ }
+
+protected:
+ SDLState& state;
+};
+
+class SDLButtonPoller final : public SDLPoller {
+public:
+ explicit SDLButtonPoller(SDLState& state_) : SDLPoller(state_) {}
+
+ Common::ParamPackage GetNextInput() override {
+ SDL_Event event;
+ while (state.event_queue.Pop(event)) {
+ switch (event.type) {
+ case SDL_JOYAXISMOTION:
+ if (std::abs(event.jaxis.value / 32767.0) < 0.5) {
+ break;
+ }
+ case SDL_JOYBUTTONUP:
+ case SDL_JOYHATMOTION:
+ return SDLEventToButtonParamPackage(state, event);
+ }
+ }
+ return {};
+ }
+};
+
+class SDLAnalogPoller final : public SDLPoller {
+public:
+ explicit SDLAnalogPoller(SDLState& state_) : SDLPoller(state_) {}
+
+ void Start() override {
+ SDLPoller::Start();
+
+ // Reset stored axes
+ analog_x_axis = -1;
+ analog_y_axis = -1;
+ analog_axes_joystick = -1;
+ }
+
+ Common::ParamPackage GetNextInput() override {
+ SDL_Event event;
+ while (state.event_queue.Pop(event)) {
+ if (event.type != SDL_JOYAXISMOTION || std::abs(event.jaxis.value / 32767.0) < 0.5) {
+ continue;
+ }
+ // An analog device needs two axes, so we need to store the axis for later and wait for
+ // a second SDL event. The axes also must be from the same joystick.
+ const int axis = event.jaxis.axis;
+ if (analog_x_axis == -1) {
+ analog_x_axis = axis;
+ analog_axes_joystick = event.jaxis.which;
+ } else if (analog_y_axis == -1 && analog_x_axis != axis &&
+ analog_axes_joystick == event.jaxis.which) {
+ analog_y_axis = axis;
+ }
+ }
+ Common::ParamPackage params;
+ if (analog_x_axis != -1 && analog_y_axis != -1) {
+ const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which);
+ params.Set("engine", "sdl");
+ params.Set("port", joystick->GetPort());
+ params.Set("guid", joystick->GetGUID());
+ params.Set("axis_x", analog_x_axis);
+ params.Set("axis_y", analog_y_axis);
+ analog_x_axis = -1;
+ analog_y_axis = -1;
+ analog_axes_joystick = -1;
+ return params;
+ }
+ return params;
+ }
+
+private:
+ int analog_x_axis = -1;
+ int analog_y_axis = -1;
+ SDL_JoystickID analog_axes_joystick = -1;
+};
+} // namespace Polling
+
+SDLState::Pollers SDLState::GetPollers(InputCommon::Polling::DeviceType type) {
+ Pollers pollers;
+
+ switch (type) {
+ case InputCommon::Polling::DeviceType::Analog:
+ pollers.emplace_back(std::make_unique<Polling::SDLAnalogPoller>(*this));
+ break;
+ case InputCommon::Polling::DeviceType::Button:
+ pollers.emplace_back(std::make_unique<Polling::SDLButtonPoller>(*this));
+ break;
+ }
+
+ return pollers;
+}
+
+} // namespace InputCommon::SDL
diff --git a/src/input_common/sdl/sdl_impl.h b/src/input_common/sdl/sdl_impl.h
new file mode 100644
index 000000000..606a32c5b
--- /dev/null
+++ b/src/input_common/sdl/sdl_impl.h
@@ -0,0 +1,72 @@
+// Copyright 2018 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <atomic>
+#include <memory>
+#include <mutex>
+#include <thread>
+#include <unordered_map>
+#include "common/common_types.h"
+#include "common/threadsafe_queue.h"
+#include "input_common/sdl/sdl.h"
+
+union SDL_Event;
+using SDL_Joystick = struct _SDL_Joystick;
+using SDL_JoystickID = s32;
+
+namespace InputCommon::SDL {
+
+class SDLAnalogFactory;
+class SDLButtonFactory;
+class SDLJoystick;
+
+class SDLState : public State {
+public:
+ /// Initializes and registers SDL device factories
+ SDLState();
+
+ /// Unregisters SDL device factories and shut them down.
+ ~SDLState() override;
+
+ /// Handle SDL_Events for joysticks from SDL_PollEvent
+ void HandleGameControllerEvent(const SDL_Event& event);
+
+ /// Get the nth joystick with the corresponding GUID
+ std::shared_ptr<SDLJoystick> GetSDLJoystickBySDLID(SDL_JoystickID sdl_id);
+
+ /**
+ * Check how many identical joysticks (by guid) were connected before the one with sdl_id and so
+ * tie it to a SDLJoystick with the same guid and that port
+ */
+ std::shared_ptr<SDLJoystick> GetSDLJoystickByGUID(const std::string& guid, int port);
+
+ /// Get all DevicePoller that use the SDL backend for a specific device type
+ Pollers GetPollers(Polling::DeviceType type) override;
+
+ /// Used by the Pollers during config
+ std::atomic<bool> polling = false;
+ Common::SPSCQueue<SDL_Event> event_queue;
+
+private:
+ void InitJoystick(int joystick_index);
+ void CloseJoystick(SDL_Joystick* sdl_joystick);
+
+ /// Needs to be called before SDL_QuitSubSystem.
+ void CloseJoysticks();
+
+ /// Map of GUID of a list of corresponding virtual Joysticks
+ std::unordered_map<std::string, std::vector<std::shared_ptr<SDLJoystick>>> joystick_map;
+ std::mutex joystick_map_mutex;
+
+ std::shared_ptr<SDLButtonFactory> button_factory;
+ std::shared_ptr<SDLAnalogFactory> analog_factory;
+
+ bool start_thread = false;
+ std::atomic<bool> initialized = false;
+
+ std::thread poll_thread;
+};
+} // namespace InputCommon::SDL
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt
index 37f09ce5f..c7038b217 100644
--- a/src/tests/CMakeLists.txt
+++ b/src/tests/CMakeLists.txt
@@ -1,4 +1,7 @@
add_executable(tests
+ common/bit_field.cpp
+ common/bit_utils.cpp
+ common/multi_level_queue.cpp
common/param_package.cpp
common/ring_buffer.cpp
core/arm/arm_test_common.cpp
diff --git a/src/tests/common/bit_field.cpp b/src/tests/common/bit_field.cpp
new file mode 100644
index 000000000..8ca1889f9
--- /dev/null
+++ b/src/tests/common/bit_field.cpp
@@ -0,0 +1,90 @@
+// Copyright 2019 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <array>
+#include <cstring>
+#include <type_traits>
+#include <catch2/catch.hpp>
+#include "common/bit_field.h"
+
+TEST_CASE("BitField", "[common]") {
+ enum class TestEnum : u32 {
+ A = 0b10111101,
+ B = 0b10101110,
+ C = 0b00001111,
+ };
+
+ union LEBitField {
+ u32_le raw;
+ BitField<0, 6, u32> a;
+ BitField<6, 4, s32> b;
+ BitField<10, 8, TestEnum> c;
+ BitField<18, 14, u32> d;
+ } le_bitfield;
+
+ union BEBitField {
+ u32_be raw;
+ BitFieldBE<0, 6, u32> a;
+ BitFieldBE<6, 4, s32> b;
+ BitFieldBE<10, 8, TestEnum> c;
+ BitFieldBE<18, 14, u32> d;
+ } be_bitfield;
+
+ static_assert(sizeof(LEBitField) == sizeof(u32));
+ static_assert(sizeof(BEBitField) == sizeof(u32));
+ static_assert(std::is_trivially_copyable_v<LEBitField>);
+ static_assert(std::is_trivially_copyable_v<BEBitField>);
+
+ std::array<u8, 4> raw{{
+ 0b01101100,
+ 0b11110110,
+ 0b10111010,
+ 0b11101100,
+ }};
+
+ std::memcpy(&le_bitfield, &raw, sizeof(raw));
+ std::memcpy(&be_bitfield, &raw, sizeof(raw));
+
+ // bit fields: 11101100101110'10111101'1001'101100
+ REQUIRE(le_bitfield.raw == 0b11101100'10111010'11110110'01101100);
+ REQUIRE(le_bitfield.a == 0b101100);
+ REQUIRE(le_bitfield.b == -7); // 1001 as two's complement
+ REQUIRE(le_bitfield.c == TestEnum::A);
+ REQUIRE(le_bitfield.d == 0b11101100101110);
+
+ le_bitfield.a.Assign(0b000111);
+ le_bitfield.b.Assign(-1);
+ le_bitfield.c.Assign(TestEnum::C);
+ le_bitfield.d.Assign(0b01010101010101);
+ std::memcpy(&raw, &le_bitfield, sizeof(raw));
+ // bit fields: 01010101010101'00001111'1111'000111
+ REQUIRE(le_bitfield.raw == 0b01010101'01010100'00111111'11000111);
+ REQUIRE(raw == std::array<u8, 4>{{
+ 0b11000111,
+ 0b00111111,
+ 0b01010100,
+ 0b01010101,
+ }});
+
+ // bit fields: 01101100111101'10101110'1011'101100
+ REQUIRE(be_bitfield.raw == 0b01101100'11110110'10111010'11101100);
+ REQUIRE(be_bitfield.a == 0b101100);
+ REQUIRE(be_bitfield.b == -5); // 1011 as two's complement
+ REQUIRE(be_bitfield.c == TestEnum::B);
+ REQUIRE(be_bitfield.d == 0b01101100111101);
+
+ be_bitfield.a.Assign(0b000111);
+ be_bitfield.b.Assign(-1);
+ be_bitfield.c.Assign(TestEnum::C);
+ 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(raw == std::array<u8, 4>{{
+ 0b01010101,
+ 0b01010100,
+ 0b00111111,
+ 0b11000111,
+ }});
+}
diff --git a/src/tests/common/bit_utils.cpp b/src/tests/common/bit_utils.cpp
new file mode 100644
index 000000000..479b5995a
--- /dev/null
+++ b/src/tests/common/bit_utils.cpp
@@ -0,0 +1,23 @@
+// 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/multi_level_queue.cpp b/src/tests/common/multi_level_queue.cpp
new file mode 100644
index 000000000..cca7ec7da
--- /dev/null
+++ b/src/tests/common/multi_level_queue.cpp
@@ -0,0 +1,55 @@
+// 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/core/arm/arm_test_common.cpp b/src/tests/core/arm/arm_test_common.cpp
index ea27ef90d..58af41f6e 100644
--- a/src/tests/core/arm/arm_test_common.cpp
+++ b/src/tests/core/arm/arm_test_common.cpp
@@ -4,6 +4,7 @@
#include <algorithm>
+#include "common/page_table.h"
#include "core/core.h"
#include "core/hle/kernel/process.h"
#include "core/memory.h"
@@ -15,19 +16,18 @@ namespace ArmTests {
TestEnvironment::TestEnvironment(bool mutable_memory_)
: mutable_memory(mutable_memory_),
test_memory(std::make_shared<TestMemory>(this)), kernel{Core::System::GetInstance()} {
- auto process = Kernel::Process::Create(kernel, "");
- kernel.MakeCurrentProcess(process.get());
+ auto process = Kernel::Process::Create(Core::System::GetInstance(), "");
page_table = &process->VMManager().page_table;
std::fill(page_table->pointers.begin(), page_table->pointers.end(), nullptr);
page_table->special_regions.clear();
std::fill(page_table->attributes.begin(), page_table->attributes.end(),
- Memory::PageType::Unmapped);
+ Common::PageType::Unmapped);
Memory::MapIoRegion(*page_table, 0x00000000, 0x80000000, test_memory);
Memory::MapIoRegion(*page_table, 0x80000000, 0x80000000, test_memory);
- Memory::SetCurrentPageTable(page_table);
+ kernel.MakeCurrentProcess(process.get());
}
TestEnvironment::~TestEnvironment() {
diff --git a/src/tests/core/arm/arm_test_common.h b/src/tests/core/arm/arm_test_common.h
index 0b7539601..d145dbfcc 100644
--- a/src/tests/core/arm/arm_test_common.h
+++ b/src/tests/core/arm/arm_test_common.h
@@ -9,10 +9,10 @@
#include <vector>
#include "common/common_types.h"
+#include "common/memory_hook.h"
#include "core/hle/kernel/kernel.h"
-#include "core/memory_hook.h"
-namespace Memory {
+namespace Common {
struct PageTable;
}
@@ -58,7 +58,7 @@ public:
private:
friend struct TestMemory;
- struct TestMemory final : Memory::MemoryHook {
+ struct TestMemory final : Common::MemoryHook {
explicit TestMemory(TestEnvironment* env_) : env(env_) {}
TestEnvironment* env;
@@ -86,7 +86,7 @@ private:
bool mutable_memory;
std::shared_ptr<TestMemory> test_memory;
std::vector<WriteRecord> write_records;
- Memory::PageTable* page_table = nullptr;
+ Common::PageTable* page_table = nullptr;
Kernel::KernelCore kernel;
};
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index 57f31cd58..f8b67cbe1 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -3,6 +3,9 @@ add_library(video_core STATIC
dma_pusher.h
debug_utils/debug_utils.cpp
debug_utils/debug_utils.h
+ engines/const_buffer_info.h
+ engines/engine_upload.cpp
+ engines/engine_upload.h
engines/fermi_2d.cpp
engines/fermi_2d.h
engines/kepler_compute.cpp
@@ -36,16 +39,18 @@ add_library(video_core STATIC
renderer_base.h
renderer_opengl/gl_buffer_cache.cpp
renderer_opengl/gl_buffer_cache.h
+ renderer_opengl/gl_device.cpp
+ renderer_opengl/gl_device.h
renderer_opengl/gl_global_cache.cpp
renderer_opengl/gl_global_cache.h
- renderer_opengl/gl_primitive_assembler.cpp
- renderer_opengl/gl_primitive_assembler.h
renderer_opengl/gl_rasterizer.cpp
renderer_opengl/gl_rasterizer.h
renderer_opengl/gl_rasterizer_cache.cpp
renderer_opengl/gl_rasterizer_cache.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
@@ -67,6 +72,8 @@ add_library(video_core STATIC
renderer_opengl/renderer_opengl.h
renderer_opengl/utils.cpp
renderer_opengl/utils.h
+ sampler_cache.cpp
+ sampler_cache.h
shader/decode/arithmetic.cpp
shader/decode/arithmetic_immediate.cpp
shader/decode/bfe.cpp
@@ -80,6 +87,7 @@ add_library(video_core STATIC
shader/decode/hfma2.cpp
shader/decode/conversion.cpp
shader/decode/memory.cpp
+ shader/decode/texture.cpp
shader/decode/float_set_predicate.cpp
shader/decode/integer_set_predicate.cpp
shader/decode/half_set_predicate.cpp
@@ -93,6 +101,9 @@ add_library(video_core STATIC
shader/decode/xmad.cpp
shader/decode/other.cpp
shader/decode.cpp
+ shader/node_helper.cpp
+ shader/node_helper.h
+ shader/node.h
shader/shader_ir.cpp
shader/shader_ir.h
shader/track.cpp
@@ -105,6 +116,8 @@ add_library(video_core STATIC
textures/decoders.cpp
textures/decoders.h
textures/texture.h
+ texture_cache.cpp
+ texture_cache.h
video_core.cpp
video_core.h
)
@@ -112,6 +125,8 @@ add_library(video_core STATIC
if (ENABLE_VULKAN)
target_sources(video_core PRIVATE
renderer_vulkan/declarations.h
+ renderer_vulkan/maxwell_to_vk.cpp
+ renderer_vulkan/maxwell_to_vk.h
renderer_vulkan/vk_buffer_cache.cpp
renderer_vulkan/vk_buffer_cache.h
renderer_vulkan/vk_device.cpp
@@ -120,16 +135,25 @@ if (ENABLE_VULKAN)
renderer_vulkan/vk_memory_manager.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_stream_buffer.cpp
- renderer_vulkan/vk_stream_buffer.h)
+ renderer_vulkan/vk_stream_buffer.h
+ renderer_vulkan/vk_swapchain.cpp
+ renderer_vulkan/vk_swapchain.h)
- target_include_directories(video_core PRIVATE ../../externals/Vulkan-Headers/include)
+ target_include_directories(video_core PRIVATE sirit ../../externals/Vulkan-Headers/include)
target_compile_definitions(video_core PRIVATE HAS_VULKAN)
endif()
create_target_directory_groups(video_core)
target_link_libraries(video_core PUBLIC common core)
-target_link_libraries(video_core PRIVATE glad lz4_static)
+target_link_libraries(video_core PRIVATE glad)
+if (ENABLE_VULKAN)
+ target_link_libraries(video_core PRIVATE sirit)
+endif()
diff --git a/src/video_core/debug_utils/debug_utils.cpp b/src/video_core/debug_utils/debug_utils.cpp
index 5ffb492ea..f0ef67535 100644
--- a/src/video_core/debug_utils/debug_utils.cpp
+++ b/src/video_core/debug_utils/debug_utils.cpp
@@ -10,7 +10,7 @@ namespace Tegra {
void DebugContext::DoOnEvent(Event event, void* data) {
{
- std::unique_lock<std::mutex> lock(breakpoint_mutex);
+ std::unique_lock lock{breakpoint_mutex};
// TODO(Subv): Commit the rasterizer's caches so framebuffers, render targets, etc. will
// show on debug widgets
@@ -32,7 +32,7 @@ void DebugContext::DoOnEvent(Event event, void* data) {
void DebugContext::Resume() {
{
- std::lock_guard<std::mutex> lock(breakpoint_mutex);
+ std::lock_guard lock{breakpoint_mutex};
// Tell all observers that we are about to resume
for (auto& breakpoint_observer : breakpoint_observers) {
diff --git a/src/video_core/debug_utils/debug_utils.h b/src/video_core/debug_utils/debug_utils.h
index c235faf46..ac3a2eb01 100644
--- a/src/video_core/debug_utils/debug_utils.h
+++ b/src/video_core/debug_utils/debug_utils.h
@@ -40,7 +40,7 @@ public:
/// Constructs the object such that it observes events of the given DebugContext.
explicit BreakPointObserver(std::shared_ptr<DebugContext> debug_context)
: context_weak(debug_context) {
- std::unique_lock<std::mutex> lock(debug_context->breakpoint_mutex);
+ std::unique_lock lock{debug_context->breakpoint_mutex};
debug_context->breakpoint_observers.push_back(this);
}
@@ -48,7 +48,7 @@ public:
auto context = context_weak.lock();
if (context) {
{
- std::unique_lock<std::mutex> lock(context->breakpoint_mutex);
+ std::unique_lock lock{context->breakpoint_mutex};
context->breakpoint_observers.remove(this);
}
diff --git a/src/video_core/dma_pusher.cpp b/src/video_core/dma_pusher.cpp
index 669541b4b..3175579cc 100644
--- a/src/video_core/dma_pusher.cpp
+++ b/src/video_core/dma_pusher.cpp
@@ -8,6 +8,7 @@
#include "video_core/dma_pusher.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/gpu.h"
+#include "video_core/memory_manager.h"
namespace Tegra {
@@ -39,7 +40,14 @@ bool DmaPusher::Step() {
}
const CommandList& command_list{dma_pushbuffer.front()};
- const CommandListHeader& command_list_header{command_list[dma_pushbuffer_subindex++]};
+ 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++]};
GPUVAddr dma_get = command_list_header.addr;
GPUVAddr dma_put = dma_get + command_list_header.size * sizeof(u32);
bool non_main = command_list_header.is_non_main;
@@ -55,12 +63,9 @@ bool DmaPusher::Step() {
}
// Push buffer non-empty, read a word
- const auto address = gpu.MemoryManager().GpuToCpuAddress(dma_get);
- ASSERT_MSG(address, "Invalid GPU address");
-
command_headers.resize(command_list_header.size);
-
- Memory::ReadBlock(*address, command_headers.data(), command_list_header.size * sizeof(u32));
+ gpu.MemoryManager().ReadBlockUnsafe(dma_get, command_headers.data(),
+ command_list_header.size * sizeof(u32));
for (const CommandHeader& command_header : command_headers) {
@@ -107,6 +112,8 @@ bool DmaPusher::Step() {
dma_state.non_incrementing = false;
dma_increment_once = true;
break;
+ default:
+ break;
}
}
}
diff --git a/src/video_core/dma_pusher.h b/src/video_core/dma_pusher.h
index 27a36348c..6ab06518f 100644
--- a/src/video_core/dma_pusher.h
+++ b/src/video_core/dma_pusher.h
@@ -9,7 +9,6 @@
#include "common/bit_field.h"
#include "common/common_types.h"
-#include "video_core/memory_manager.h"
namespace Tegra {
diff --git a/src/video_core/engines/const_buffer_info.h b/src/video_core/engines/const_buffer_info.h
new file mode 100644
index 000000000..d8f672462
--- /dev/null
+++ b/src/video_core/engines/const_buffer_info.h
@@ -0,0 +1,17 @@
+// 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 Tegra::Engines {
+
+struct ConstBufferInfo {
+ GPUVAddr address;
+ u32 size;
+ bool enabled;
+};
+
+} // namespace Tegra::Engines
diff --git a/src/video_core/engines/engine_upload.cpp b/src/video_core/engines/engine_upload.cpp
new file mode 100644
index 000000000..082a40cd9
--- /dev/null
+++ b/src/video_core/engines/engine_upload.cpp
@@ -0,0 +1,52 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cstring>
+
+#include "common/assert.h"
+#include "video_core/engines/engine_upload.h"
+#include "video_core/memory_manager.h"
+#include "video_core/textures/decoders.h"
+
+namespace Tegra::Engines::Upload {
+
+State::State(MemoryManager& memory_manager, Registers& regs)
+ : regs{regs}, memory_manager{memory_manager} {}
+
+State::~State() = default;
+
+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;
+}
+
+void State::ProcessData(const u32 data, const bool is_last_call) {
+ const u32 sub_copy_size = std::min(4U, copy_size - write_offset);
+ std::memcpy(&inner_buffer[write_offset], &data, sub_copy_size);
+ write_offset += sub_copy_size;
+ if (!is_last_call) {
+ return;
+ }
+ const GPUVAddr address{regs.dest.Address()};
+ if (is_linear) {
+ memory_manager.WriteBlock(address, inner_buffer.data(), copy_size);
+ } else {
+ UNIMPLEMENTED_IF(regs.dest.z != 0);
+ UNIMPLEMENTED_IF(regs.dest.depth != 1);
+ UNIMPLEMENTED_IF(regs.dest.BlockWidth() != 1);
+ UNIMPLEMENTED_IF(regs.dest.BlockDepth() != 1);
+ const std::size_t dst_size = Tegra::Texture::CalculateSize(
+ true, 1, regs.dest.width, regs.dest.height, 1, regs.dest.BlockHeight(), 1);
+ tmp_buffer.resize(dst_size);
+ memory_manager.ReadBlock(address, tmp_buffer.data(), dst_size);
+ Tegra::Texture::SwizzleKepler(regs.dest.width, regs.dest.height, regs.dest.x, regs.dest.y,
+ regs.dest.BlockHeight(), copy_size, inner_buffer.data(),
+ tmp_buffer.data());
+ memory_manager.WriteBlock(address, tmp_buffer.data(), dst_size);
+ }
+}
+
+} // namespace Tegra::Engines::Upload
diff --git a/src/video_core/engines/engine_upload.h b/src/video_core/engines/engine_upload.h
new file mode 100644
index 000000000..ef4f5839a
--- /dev/null
+++ b/src/video_core/engines/engine_upload.h
@@ -0,0 +1,73 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <vector>
+#include "common/bit_field.h"
+#include "common/common_types.h"
+
+namespace Tegra {
+class MemoryManager;
+}
+
+namespace Tegra::Engines::Upload {
+
+struct Registers {
+ u32 line_length_in;
+ u32 line_count;
+
+ struct {
+ u32 address_high;
+ u32 address_low;
+ u32 pitch;
+ union {
+ BitField<0, 4, u32> block_width;
+ BitField<4, 4, u32> block_height;
+ BitField<8, 4, u32> block_depth;
+ };
+ u32 width;
+ u32 height;
+ u32 depth;
+ u32 z;
+ u32 x;
+ u32 y;
+
+ GPUVAddr Address() const {
+ return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) | address_low);
+ }
+
+ u32 BlockWidth() const {
+ return 1U << block_width.Value();
+ }
+
+ u32 BlockHeight() const {
+ return 1U << block_height.Value();
+ }
+
+ u32 BlockDepth() const {
+ return 1U << block_depth.Value();
+ }
+ } dest;
+};
+
+class State {
+public:
+ State(MemoryManager& memory_manager, Registers& regs);
+ ~State();
+
+ void ProcessExec(bool is_linear);
+ void ProcessData(u32 data, bool is_last_call);
+
+private:
+ u32 write_offset = 0;
+ u32 copy_size = 0;
+ std::vector<u8> inner_buffer;
+ std::vector<u8> tmp_buffer;
+ bool is_linear = false;
+ Registers& regs;
+ MemoryManager& memory_manager;
+};
+
+} // namespace Tegra::Engines::Upload
diff --git a/src/video_core/engines/fermi_2d.cpp b/src/video_core/engines/fermi_2d.cpp
index 03b7ee5d8..55966eef1 100644
--- a/src/video_core/engines/fermi_2d.cpp
+++ b/src/video_core/engines/fermi_2d.cpp
@@ -6,12 +6,13 @@
#include "common/logging/log.h"
#include "common/math_util.h"
#include "video_core/engines/fermi_2d.h"
+#include "video_core/memory_manager.h"
#include "video_core/rasterizer_interface.h"
namespace Tegra::Engines {
Fermi2D::Fermi2D(VideoCore::RasterizerInterface& rasterizer, MemoryManager& memory_manager)
- : memory_manager(memory_manager), rasterizer{rasterizer} {}
+ : rasterizer{rasterizer}, memory_manager{memory_manager} {}
void Fermi2D::CallMethod(const GPU::MethodCall& method_call) {
ASSERT_MSG(method_call.method < Regs::NUM_REGS,
diff --git a/src/video_core/engines/fermi_2d.h b/src/video_core/engines/fermi_2d.h
index 80523e320..45f59a4d9 100644
--- a/src/video_core/engines/fermi_2d.h
+++ b/src/video_core/engines/fermi_2d.h
@@ -10,7 +10,10 @@
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "video_core/gpu.h"
-#include "video_core/memory_manager.h"
+
+namespace Tegra {
+class MemoryManager;
+}
namespace VideoCore {
class RasterizerInterface;
@@ -18,6 +21,12 @@ class RasterizerInterface;
namespace Tegra::Engines {
+/**
+ * This Engine is known as G80_2D. Documentation can be found in:
+ * https://github.com/envytools/envytools/blob/master/rnndb/graph/g80_2d.xml
+ * https://cgit.freedesktop.org/mesa/mesa/tree/src/gallium/drivers/nouveau/nv50/nv50_2d.xml.h
+ */
+
#define FERMI2D_REG_INDEX(field_name) \
(offsetof(Tegra::Engines::Fermi2D::Regs, field_name) / sizeof(u32))
@@ -115,10 +124,9 @@ public:
};
} regs{};
- MemoryManager& memory_manager;
-
private:
VideoCore::RasterizerInterface& rasterizer;
+ MemoryManager& memory_manager;
/// Performs the copy from the source surface to the destination surface as configured in the
/// registers.
diff --git a/src/video_core/engines/kepler_compute.cpp b/src/video_core/engines/kepler_compute.cpp
index b1d950460..7404a8163 100644
--- a/src/video_core/engines/kepler_compute.cpp
+++ b/src/video_core/engines/kepler_compute.cpp
@@ -4,12 +4,21 @@
#include "common/assert.h"
#include "common/logging/log.h"
+#include "core/core.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/renderer_base.h"
+#include "video_core/textures/decoders.h"
namespace Tegra::Engines {
-KeplerCompute::KeplerCompute(MemoryManager& memory_manager) : memory_manager{memory_manager} {}
+KeplerCompute::KeplerCompute(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
+ MemoryManager& memory_manager)
+ : system{system}, rasterizer{rasterizer}, memory_manager{memory_manager}, upload_state{
+ memory_manager,
+ regs.upload} {}
KeplerCompute::~KeplerCompute() = default;
@@ -20,14 +29,34 @@ void KeplerCompute::CallMethod(const GPU::MethodCall& method_call) {
regs.reg_array[method_call.method] = method_call.argument;
switch (method_call.method) {
+ case KEPLER_COMPUTE_REG_INDEX(exec_upload): {
+ upload_state.ProcessExec(regs.exec_upload.linear != 0);
+ break;
+ }
+ case KEPLER_COMPUTE_REG_INDEX(data_upload): {
+ const bool is_last_call = method_call.IsLastCall();
+ upload_state.ProcessData(method_call.argument, is_last_call);
+ if (is_last_call) {
+ system.GPU().Maxwell3D().dirty_flags.OnMemoryWrite();
+ }
+ break;
+ }
case KEPLER_COMPUTE_REG_INDEX(launch):
- // Abort execution since compute shaders can be used to alter game memory (e.g. CUDA
- // kernels)
- UNREACHABLE_MSG("Compute shaders are not implemented");
+ ProcessLaunch();
break;
default:
break;
}
}
+void KeplerCompute::ProcessLaunch() {
+
+ const GPUVAddr launch_desc_loc = regs.launch_desc_loc.Address();
+ memory_manager.ReadBlockUnsafe(launch_desc_loc, &launch_description,
+ LaunchParams::NUM_LAUNCH_PARAMETERS * sizeof(u32));
+
+ const GPUVAddr code_loc = regs.code_loc.Address() + launch_description.program_start;
+ LOG_WARNING(HW_GPU, "Compute Kernel Execute at Address 0x{:016x}, STUBBED", code_loc);
+}
+
} // namespace Tegra::Engines
diff --git a/src/video_core/engines/kepler_compute.h b/src/video_core/engines/kepler_compute.h
index 6575afd0f..6a3309a2c 100644
--- a/src/video_core/engines/kepler_compute.h
+++ b/src/video_core/engines/kepler_compute.h
@@ -6,19 +6,40 @@
#include <array>
#include <cstddef>
+#include <vector>
+#include "common/bit_field.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
+#include "video_core/engines/engine_upload.h"
#include "video_core/gpu.h"
-#include "video_core/memory_manager.h"
+
+namespace Core {
+class System;
+}
+
+namespace Tegra {
+class MemoryManager;
+}
+
+namespace VideoCore {
+class RasterizerInterface;
+}
namespace Tegra::Engines {
+/**
+ * This Engine is known as GK104_Compute. Documentation can be found in:
+ * https://github.com/envytools/envytools/blob/master/rnndb/graph/gk104_compute.xml
+ * https://cgit.freedesktop.org/mesa/mesa/tree/src/gallium/drivers/nouveau/nvc0/nve4_compute.xml.h
+ */
+
#define KEPLER_COMPUTE_REG_INDEX(field_name) \
(offsetof(Tegra::Engines::KeplerCompute::Regs, field_name) / sizeof(u32))
class KeplerCompute final {
public:
- explicit KeplerCompute(MemoryManager& memory_manager);
+ explicit KeplerCompute(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
+ MemoryManager& memory_manager);
~KeplerCompute();
static constexpr std::size_t NumConstBuffers = 8;
@@ -28,29 +49,182 @@ public:
union {
struct {
- INSERT_PADDING_WORDS(0xAF);
+ INSERT_PADDING_WORDS(0x60);
+
+ Upload::Registers upload;
+
+ struct {
+ union {
+ BitField<0, 1, u32> linear;
+ };
+ } exec_upload;
+
+ u32 data_upload;
+
+ INSERT_PADDING_WORDS(0x3F);
+
+ struct {
+ u32 address;
+ GPUVAddr Address() const {
+ return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address) << 8));
+ }
+ } launch_desc_loc;
+
+ INSERT_PADDING_WORDS(0x1);
u32 launch;
- INSERT_PADDING_WORDS(0xC48);
+ INSERT_PADDING_WORDS(0x4A7);
+
+ struct {
+ u32 address_high;
+ u32 address_low;
+ u32 limit;
+ GPUVAddr Address() const {
+ return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) |
+ address_low);
+ }
+ } tsc;
+
+ INSERT_PADDING_WORDS(0x3);
+
+ struct {
+ u32 address_high;
+ u32 address_low;
+ u32 limit;
+ GPUVAddr Address() const {
+ return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) |
+ address_low);
+ }
+ } tic;
+
+ INSERT_PADDING_WORDS(0x22);
+
+ struct {
+ u32 address_high;
+ u32 address_low;
+ GPUVAddr Address() const {
+ return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) |
+ address_low);
+ }
+ } code_loc;
+
+ INSERT_PADDING_WORDS(0x3FE);
+
+ u32 texture_const_buffer_index;
+
+ INSERT_PADDING_WORDS(0x374);
};
std::array<u32, NUM_REGS> reg_array;
};
} regs{};
+
+ struct LaunchParams {
+ static constexpr std::size_t NUM_LAUNCH_PARAMETERS = 0x40;
+
+ INSERT_PADDING_WORDS(0x8);
+
+ u32 program_start;
+
+ INSERT_PADDING_WORDS(0x2);
+
+ BitField<30, 1, u32> linked_tsc;
+
+ BitField<0, 31, u32> grid_dim_x;
+ union {
+ BitField<0, 16, u32> grid_dim_y;
+ BitField<16, 16, u32> grid_dim_z;
+ };
+
+ INSERT_PADDING_WORDS(0x3);
+
+ BitField<0, 16, u32> shared_alloc;
+
+ BitField<16, 16, u32> block_dim_x;
+ union {
+ BitField<0, 16, u32> block_dim_y;
+ BitField<16, 16, u32> block_dim_z;
+ };
+
+ union {
+ BitField<0, 8, u32> const_buffer_enable_mask;
+ BitField<29, 2, u32> cache_layout;
+ } memory_config;
+
+ INSERT_PADDING_WORDS(0x8);
+
+ struct ConstBufferConfig {
+ u32 address_low;
+ union {
+ BitField<0, 8, u32> address_high;
+ BitField<15, 17, u32> size;
+ };
+ GPUVAddr Address() const {
+ return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high.Value()) << 32) |
+ address_low);
+ }
+ };
+ std::array<ConstBufferConfig, NumConstBuffers> const_buffer_config;
+
+ union {
+ BitField<0, 20, u32> local_pos_alloc;
+ BitField<27, 5, u32> barrier_alloc;
+ };
+
+ union {
+ BitField<0, 20, u32> local_neg_alloc;
+ BitField<24, 5, u32> gpr_alloc;
+ };
+
+ INSERT_PADDING_WORDS(0x11);
+ } launch_description;
+
+ struct {
+ u32 write_offset = 0;
+ u32 copy_size = 0;
+ std::vector<u8> inner_buffer;
+ } state{};
+
static_assert(sizeof(Regs) == Regs::NUM_REGS * sizeof(u32),
"KeplerCompute Regs has wrong size");
- MemoryManager& memory_manager;
+ static_assert(sizeof(LaunchParams) == LaunchParams::NUM_LAUNCH_PARAMETERS * sizeof(u32),
+ "KeplerCompute LaunchParams has wrong size");
/// Write the value to the register identified by method.
void CallMethod(const GPU::MethodCall& method_call);
+
+private:
+ Core::System& system;
+ VideoCore::RasterizerInterface& rasterizer;
+ MemoryManager& memory_manager;
+ Upload::State upload_state;
+
+ void ProcessLaunch();
};
#define ASSERT_REG_POSITION(field_name, position) \
static_assert(offsetof(KeplerCompute::Regs, field_name) == position * 4, \
"Field " #field_name " has invalid position")
+#define ASSERT_LAUNCH_PARAM_POSITION(field_name, position) \
+ static_assert(offsetof(KeplerCompute::LaunchParams, field_name) == position * 4, \
+ "Field " #field_name " has invalid position")
+
+ASSERT_REG_POSITION(upload, 0x60);
+ASSERT_REG_POSITION(exec_upload, 0x6C);
+ASSERT_REG_POSITION(data_upload, 0x6D);
ASSERT_REG_POSITION(launch, 0xAF);
+ASSERT_REG_POSITION(tsc, 0x557);
+ASSERT_REG_POSITION(tic, 0x55D);
+ASSERT_REG_POSITION(code_loc, 0x582);
+ASSERT_REG_POSITION(texture_const_buffer_index, 0x982);
+ASSERT_LAUNCH_PARAM_POSITION(program_start, 0x8);
+ASSERT_LAUNCH_PARAM_POSITION(grid_dim_x, 0xC);
+ASSERT_LAUNCH_PARAM_POSITION(shared_alloc, 0x11);
+ASSERT_LAUNCH_PARAM_POSITION(block_dim_x, 0x12);
+ASSERT_LAUNCH_PARAM_POSITION(memory_config, 0x14);
+ASSERT_LAUNCH_PARAM_POSITION(const_buffer_config, 0x1D);
#undef ASSERT_REG_POSITION
diff --git a/src/video_core/engines/kepler_memory.cpp b/src/video_core/engines/kepler_memory.cpp
index aae2a4019..0561f676c 100644
--- a/src/video_core/engines/kepler_memory.cpp
+++ b/src/video_core/engines/kepler_memory.cpp
@@ -5,16 +5,17 @@
#include "common/assert.h"
#include "common/logging/log.h"
#include "core/core.h"
-#include "core/memory.h"
#include "video_core/engines/kepler_memory.h"
#include "video_core/engines/maxwell_3d.h"
+#include "video_core/memory_manager.h"
#include "video_core/rasterizer_interface.h"
+#include "video_core/renderer_base.h"
+#include "video_core/textures/decoders.h"
namespace Tegra::Engines {
-KeplerMemory::KeplerMemory(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
- MemoryManager& memory_manager)
- : system{system}, memory_manager(memory_manager), rasterizer{rasterizer} {}
+KeplerMemory::KeplerMemory(Core::System& system, MemoryManager& memory_manager)
+ : system{system}, memory_manager{memory_manager}, upload_state{memory_manager, regs.upload} {}
KeplerMemory::~KeplerMemory() = default;
@@ -26,34 +27,18 @@ void KeplerMemory::CallMethod(const GPU::MethodCall& method_call) {
switch (method_call.method) {
case KEPLERMEMORY_REG_INDEX(exec): {
- state.write_offset = 0;
+ upload_state.ProcessExec(regs.exec.linear != 0);
break;
}
case KEPLERMEMORY_REG_INDEX(data): {
- ProcessData(method_call.argument);
+ const bool is_last_call = method_call.IsLastCall();
+ upload_state.ProcessData(method_call.argument, is_last_call);
+ if (is_last_call) {
+ system.GPU().Maxwell3D().dirty_flags.OnMemoryWrite();
+ }
break;
}
}
}
-void KeplerMemory::ProcessData(u32 data) {
- ASSERT_MSG(regs.exec.linear, "Non-linear uploads are not supported");
- ASSERT(regs.dest.x == 0 && regs.dest.y == 0 && regs.dest.z == 0);
-
- const GPUVAddr address = regs.dest.Address();
- const auto dest_address =
- memory_manager.GpuToCpuAddress(address + state.write_offset * sizeof(u32));
- ASSERT_MSG(dest_address, "Invalid GPU address");
-
- // We have to invalidate the destination region to evict any outdated surfaces from the cache.
- // We do this before actually writing the new data because the destination address might contain
- // a dirty surface that will have to be written back to memory.
- Core::System::GetInstance().GPU().InvalidateRegion(*dest_address, sizeof(u32));
-
- Memory::Write32(*dest_address, data);
- system.GPU().Maxwell3D().dirty_flags.OnMemoryWrite();
-
- state.write_offset++;
-}
-
} // namespace Tegra::Engines
diff --git a/src/video_core/engines/kepler_memory.h b/src/video_core/engines/kepler_memory.h
index 9181e9d80..f3bc675a9 100644
--- a/src/video_core/engines/kepler_memory.h
+++ b/src/video_core/engines/kepler_memory.h
@@ -6,29 +6,35 @@
#include <array>
#include <cstddef>
+#include <vector>
#include "common/bit_field.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
+#include "video_core/engines/engine_upload.h"
#include "video_core/gpu.h"
-#include "video_core/memory_manager.h"
namespace Core {
class System;
}
-namespace VideoCore {
-class RasterizerInterface;
+namespace Tegra {
+class MemoryManager;
}
namespace Tegra::Engines {
+/**
+ * This Engine is known as P2MF. Documentation can be found in:
+ * https://github.com/envytools/envytools/blob/master/rnndb/graph/gk104_p2mf.xml
+ * https://cgit.freedesktop.org/mesa/mesa/tree/src/gallium/drivers/nouveau/nvc0/nve4_p2mf.xml.h
+ */
+
#define KEPLERMEMORY_REG_INDEX(field_name) \
(offsetof(Tegra::Engines::KeplerMemory::Regs, field_name) / sizeof(u32))
class KeplerMemory final {
public:
- KeplerMemory(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
- MemoryManager& memory_manager);
+ KeplerMemory(Core::System& system, MemoryManager& memory_manager);
~KeplerMemory();
/// Write the value to the register identified by method.
@@ -41,26 +47,7 @@ public:
struct {
INSERT_PADDING_WORDS(0x60);
- u32 line_length_in;
- u32 line_count;
-
- struct {
- u32 address_high;
- u32 address_low;
- u32 pitch;
- u32 block_dimensions;
- u32 width;
- u32 height;
- u32 depth;
- u32 z;
- u32 x;
- u32 y;
-
- GPUVAddr Address() const {
- return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) |
- address_low);
- }
- } dest;
+ Upload::Registers upload;
struct {
union {
@@ -76,25 +63,17 @@ public:
};
} regs{};
- struct {
- u32 write_offset = 0;
- } state{};
-
private:
Core::System& system;
MemoryManager& memory_manager;
- VideoCore::RasterizerInterface& rasterizer;
-
- void ProcessData(u32 data);
+ Upload::State upload_state;
};
#define ASSERT_REG_POSITION(field_name, position) \
static_assert(offsetof(KeplerMemory::Regs, field_name) == position * 4, \
"Field " #field_name " has invalid position")
-ASSERT_REG_POSITION(line_length_in, 0x60);
-ASSERT_REG_POSITION(line_count, 0x61);
-ASSERT_REG_POSITION(dest, 0x62);
+ASSERT_REG_POSITION(upload, 0x60);
ASSERT_REG_POSITION(exec, 0x6C);
ASSERT_REG_POSITION(data, 0x6D);
#undef ASSERT_REG_POSITION
diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp
index 144e7fa82..08d553696 100644
--- a/src/video_core/engines/maxwell_3d.cpp
+++ b/src/video_core/engines/maxwell_3d.cpp
@@ -7,11 +7,10 @@
#include "common/assert.h"
#include "core/core.h"
#include "core/core_timing.h"
-#include "core/memory.h"
#include "video_core/debug_utils/debug_utils.h"
#include "video_core/engines/maxwell_3d.h"
+#include "video_core/memory_manager.h"
#include "video_core/rasterizer_interface.h"
-#include "video_core/renderer_base.h"
#include "video_core/textures/texture.h"
namespace Tegra::Engines {
@@ -21,8 +20,8 @@ constexpr u32 MacroRegistersStart = 0xE00;
Maxwell3D::Maxwell3D(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
MemoryManager& memory_manager)
- : memory_manager(memory_manager), system{system}, rasterizer{rasterizer},
- macro_interpreter(*this) {
+ : system{system}, rasterizer{rasterizer}, memory_manager{memory_manager},
+ macro_interpreter{*this}, upload_state{memory_manager, regs.upload} {
InitializeRegisterDefaults();
}
@@ -35,9 +34,9 @@ void Maxwell3D::InitializeRegisterDefaults() {
// Depth range near/far is not always set, but is expected to be the default 0.0f, 1.0f. This is
// needed for ARMS.
- for (std::size_t viewport{}; viewport < Regs::NumViewports; ++viewport) {
- regs.viewports[viewport].depth_range_near = 0.0f;
- regs.viewports[viewport].depth_range_far = 1.0f;
+ for (auto& viewport : regs.viewports) {
+ viewport.depth_range_near = 0.0f;
+ viewport.depth_range_far = 1.0f;
}
// Doom and Bomberman seems to use the uninitialized registers and just enable blend
@@ -48,13 +47,13 @@ void Maxwell3D::InitializeRegisterDefaults() {
regs.blend.equation_a = Regs::Blend::Equation::Add;
regs.blend.factor_source_a = Regs::Blend::Factor::One;
regs.blend.factor_dest_a = Regs::Blend::Factor::Zero;
- for (std::size_t blend_index = 0; blend_index < Regs::NumRenderTargets; blend_index++) {
- regs.independent_blend[blend_index].equation_rgb = Regs::Blend::Equation::Add;
- regs.independent_blend[blend_index].factor_source_rgb = Regs::Blend::Factor::One;
- regs.independent_blend[blend_index].factor_dest_rgb = Regs::Blend::Factor::Zero;
- regs.independent_blend[blend_index].equation_a = Regs::Blend::Equation::Add;
- regs.independent_blend[blend_index].factor_source_a = Regs::Blend::Factor::One;
- regs.independent_blend[blend_index].factor_dest_a = Regs::Blend::Factor::Zero;
+ for (auto& blend : regs.independent_blend) {
+ blend.equation_rgb = Regs::Blend::Equation::Add;
+ blend.factor_source_rgb = Regs::Blend::Factor::One;
+ blend.factor_dest_rgb = Regs::Blend::Factor::Zero;
+ blend.equation_a = Regs::Blend::Equation::Add;
+ blend.factor_source_a = Regs::Blend::Factor::One;
+ blend.factor_dest_a = Regs::Blend::Factor::Zero;
}
regs.stencil_front_op_fail = Regs::StencilOp::Keep;
regs.stencil_front_op_zfail = Regs::StencilOp::Keep;
@@ -76,11 +75,11 @@ void Maxwell3D::InitializeRegisterDefaults() {
// TODO(bunnei): Some games do not initialize the color masks (e.g. Sonic Mania). Assuming a
// default of enabled fixes rendering here.
- for (std::size_t color_mask = 0; color_mask < Regs::NumRenderTargets; color_mask++) {
- regs.color_mask[color_mask].R.Assign(1);
- regs.color_mask[color_mask].G.Assign(1);
- regs.color_mask[color_mask].B.Assign(1);
- regs.color_mask[color_mask].A.Assign(1);
+ for (auto& color_mask : regs.color_mask) {
+ color_mask.R.Assign(1);
+ color_mask.G.Assign(1);
+ color_mask.B.Assign(1);
+ color_mask.A.Assign(1);
}
// Commercial games seem to assume this value is enabled and nouveau sets this value manually.
@@ -179,13 +178,13 @@ void Maxwell3D::CallMethod(const GPU::MethodCall& method_call) {
// Vertex buffer
if (method >= MAXWELL3D_REG_INDEX(vertex_array) &&
- method < MAXWELL3D_REG_INDEX(vertex_array) + 4 * 32) {
+ method < MAXWELL3D_REG_INDEX(vertex_array) + 4 * Regs::NumVertexArrays) {
dirty_flags.vertex_array.set((method - MAXWELL3D_REG_INDEX(vertex_array)) >> 2);
} else if (method >= MAXWELL3D_REG_INDEX(vertex_array_limit) &&
- method < MAXWELL3D_REG_INDEX(vertex_array_limit) + 2 * 32) {
+ method < MAXWELL3D_REG_INDEX(vertex_array_limit) + 2 * Regs::NumVertexArrays) {
dirty_flags.vertex_array.set((method - MAXWELL3D_REG_INDEX(vertex_array_limit)) >> 1);
} else if (method >= MAXWELL3D_REG_INDEX(instanced_arrays) &&
- method < MAXWELL3D_REG_INDEX(instanced_arrays) + 32) {
+ method < MAXWELL3D_REG_INDEX(instanced_arrays) + Regs::NumVertexArrays) {
dirty_flags.vertex_array.set(method - MAXWELL3D_REG_INDEX(instanced_arrays));
}
}
@@ -250,6 +249,22 @@ void Maxwell3D::CallMethod(const GPU::MethodCall& method_call) {
ProcessQueryGet();
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): {
+ const bool is_last_call = method_call.IsLastCall();
+ upload_state.ProcessData(method_call.argument, is_last_call);
+ if (is_last_call) {
+ dirty_flags.OnMemoryWrite();
+ }
+ break;
+ }
default:
break;
}
@@ -270,11 +285,9 @@ void Maxwell3D::ProcessMacroBind(u32 data) {
}
void Maxwell3D::ProcessQueryGet() {
- GPUVAddr sequence_address = regs.query.QueryAddress();
+ const GPUVAddr sequence_address{regs.query.QueryAddress()};
// Since the sequence address is given as a GPU VAddr, we have to convert it to an application
// VAddr before writing.
- const auto address = memory_manager.GpuToCpuAddress(sequence_address);
- ASSERT_MSG(address, "Invalid GPU address");
// TODO(Subv): Support the other query units.
ASSERT_MSG(regs.query.query_get.unit == Regs::QueryUnit::Crop,
@@ -309,7 +322,7 @@ void Maxwell3D::ProcessQueryGet() {
// Write the current query sequence to the sequence address.
// TODO(Subv): Find out what happens if you use a long query type but mark it as a short
// query.
- Memory::Write32(*address, sequence);
+ memory_manager.Write<u32>(sequence_address, sequence);
} else {
// Write the 128-bit result structure in long mode. Note: We emulate an infinitely fast
// GPU, this command may actually take a while to complete in real hardware due to GPU
@@ -318,7 +331,7 @@ void Maxwell3D::ProcessQueryGet() {
query_result.value = result;
// TODO(Subv): Generate a real GPU timestamp and write it here instead of CoreTiming
query_result.timestamp = system.CoreTiming().GetTicks();
- Memory::WriteBlock(*address, &query_result, sizeof(query_result));
+ memory_manager.WriteBlock(sequence_address, &query_result, sizeof(query_result));
}
dirty_flags.OnMemoryWrite();
break;
@@ -329,6 +342,14 @@ void Maxwell3D::ProcessQueryGet() {
}
}
+void Maxwell3D::ProcessSyncPoint() {
+ const u32 sync_point = regs.sync_info.sync_point.Value();
+ const u32 increment = regs.sync_info.increment.Value();
+ const u32 cache_flush = regs.sync_info.unknown.Value();
+ LOG_DEBUG(HW_GPU, "Syncpoint set {}, increment: {}, unk: {}", sync_point, increment,
+ cache_flush);
+}
+
void Maxwell3D::DrawArrays() {
LOG_DEBUG(HW_GPU, "called, topology={}, count={}", static_cast<u32>(regs.draw.topology.Value()),
regs.vertex_buffer.count);
@@ -375,12 +396,10 @@ void Maxwell3D::ProcessCBBind(Regs::ShaderStage stage) {
auto& shader = state.shader_stages[static_cast<std::size_t>(stage)];
auto& bind_data = regs.cb_bind[static_cast<std::size_t>(stage)];
- auto& buffer = shader.const_buffers[bind_data.index];
-
ASSERT(bind_data.index < Regs::MaxConstBuffers);
+ auto& buffer = shader.const_buffers[bind_data.index];
buffer.enabled = bind_data.valid.Value() != 0;
- buffer.index = bind_data.index;
buffer.address = regs.const_buffer.BufferAddress();
buffer.size = regs.const_buffer.cb_size;
}
@@ -393,10 +412,12 @@ void Maxwell3D::ProcessCBData(u32 value) {
// Don't allow writing past the end of the buffer.
ASSERT(regs.const_buffer.cb_pos + sizeof(u32) <= regs.const_buffer.cb_size);
- const auto address = memory_manager.GpuToCpuAddress(buffer_address + regs.const_buffer.cb_pos);
- ASSERT_MSG(address, "Invalid GPU address");
+ const GPUVAddr address{buffer_address + regs.const_buffer.cb_pos};
+
+ u8* ptr{memory_manager.GetPointer(address)};
+ rasterizer.InvalidateRegion(ToCacheAddr(ptr), sizeof(u32));
+ memory_manager.Write<u32>(address, value);
- Memory::Write32(*address, value);
dirty_flags.OnMemoryWrite();
// Increment the current buffer position.
@@ -404,14 +425,10 @@ void Maxwell3D::ProcessCBData(u32 value) {
}
Texture::TICEntry Maxwell3D::GetTICEntry(u32 tic_index) const {
- const GPUVAddr tic_base_address = regs.tic.TICAddress();
-
- const GPUVAddr tic_address_gpu = tic_base_address + tic_index * sizeof(Texture::TICEntry);
- const auto tic_address_cpu = memory_manager.GpuToCpuAddress(tic_address_gpu);
- ASSERT_MSG(tic_address_cpu, "Invalid GPU address");
+ const GPUVAddr tic_address_gpu{regs.tic.TICAddress() + tic_index * sizeof(Texture::TICEntry)};
Texture::TICEntry tic_entry;
- Memory::ReadBlock(*tic_address_cpu, &tic_entry, sizeof(Texture::TICEntry));
+ memory_manager.ReadBlockUnsafe(tic_address_gpu, &tic_entry, sizeof(Texture::TICEntry));
ASSERT_MSG(tic_entry.header_version == Texture::TICHeaderVersion::BlockLinear ||
tic_entry.header_version == Texture::TICHeaderVersion::Pitch,
@@ -423,20 +440,16 @@ Texture::TICEntry Maxwell3D::GetTICEntry(u32 tic_index) const {
const auto a_type = tic_entry.a_type.Value();
// TODO(Subv): Different data types for separate components are not supported
- ASSERT(r_type == g_type && r_type == b_type && r_type == a_type);
+ DEBUG_ASSERT(r_type == g_type && r_type == b_type && r_type == a_type);
return tic_entry;
}
Texture::TSCEntry Maxwell3D::GetTSCEntry(u32 tsc_index) const {
- const GPUVAddr tsc_base_address = regs.tsc.TSCAddress();
-
- const GPUVAddr tsc_address_gpu = tsc_base_address + tsc_index * sizeof(Texture::TSCEntry);
- const auto tsc_address_cpu = memory_manager.GpuToCpuAddress(tsc_address_gpu);
- ASSERT_MSG(tsc_address_cpu, "Invalid GPU address");
+ const GPUVAddr tsc_address_gpu{regs.tsc.TSCAddress() + tsc_index * sizeof(Texture::TSCEntry)};
Texture::TSCEntry tsc_entry;
- Memory::ReadBlock(*tsc_address_cpu, &tsc_entry, sizeof(Texture::TSCEntry));
+ memory_manager.ReadBlockUnsafe(tsc_address_gpu, &tsc_entry, sizeof(Texture::TSCEntry));
return tsc_entry;
}
@@ -455,10 +468,7 @@ std::vector<Texture::FullTextureInfo> Maxwell3D::GetStageTextures(Regs::ShaderSt
for (GPUVAddr current_texture = tex_info_buffer.address + TextureInfoOffset;
current_texture < tex_info_buffer_end; current_texture += sizeof(Texture::TextureHandle)) {
- const auto address = memory_manager.GpuToCpuAddress(current_texture);
- ASSERT_MSG(address, "Invalid GPU address");
-
- const Texture::TextureHandle tex_handle{Memory::Read32(*address)};
+ const Texture::TextureHandle tex_handle{memory_manager.Read<u32>(current_texture)};
Texture::FullTextureInfo tex_info{};
// TODO(Subv): Use the shader to determine which textures are actually accessed.
@@ -482,22 +492,8 @@ std::vector<Texture::FullTextureInfo> Maxwell3D::GetStageTextures(Regs::ShaderSt
return textures;
}
-Texture::FullTextureInfo Maxwell3D::GetStageTexture(Regs::ShaderStage stage,
- std::size_t offset) const {
- auto& shader = state.shader_stages[static_cast<std::size_t>(stage)];
- 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 auto tex_address_cpu = memory_manager.GpuToCpuAddress(tex_info_address);
- ASSERT_MSG(tex_address_cpu, "Invalid GPU address");
-
- const Texture::TextureHandle tex_handle{Memory::Read32(*tex_address_cpu)};
-
+Texture::FullTextureInfo Maxwell3D::GetTextureInfo(const Texture::TextureHandle tex_handle,
+ std::size_t offset) const {
Texture::FullTextureInfo tex_info{};
tex_info.index = static_cast<u32>(offset);
@@ -514,6 +510,22 @@ Texture::FullTextureInfo Maxwell3D::GetStageTexture(Regs::ShaderStage stage,
return tex_info;
}
+Texture::FullTextureInfo Maxwell3D::GetStageTexture(Regs::ShaderStage stage,
+ std::size_t offset) const {
+ const auto& shader = state.shader_stages[static_cast<std::size_t>(stage)];
+ 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, offset);
+}
+
u32 Maxwell3D::GetRegisterValue(u32 method) const {
ASSERT_MSG(method < Regs::NUM_REGS, "Invalid Maxwell3D register");
return regs.reg_array[method];
@@ -527,4 +539,12 @@ void Maxwell3D::ProcessClearBuffers() {
rasterizer.Clear();
}
+u32 Maxwell3D::AccessConstBuffer32(Regs::ShaderStage stage, u64 const_buffer, u64 offset) const {
+ 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;
+}
+
} // namespace Tegra::Engines
diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h
index 7fbf1026e..13e314944 100644
--- a/src/video_core/engines/maxwell_3d.h
+++ b/src/video_core/engines/maxwell_3d.h
@@ -6,6 +6,7 @@
#include <array>
#include <bitset>
+#include <type_traits>
#include <unordered_map>
#include <vector>
@@ -14,21 +15,32 @@
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/math_util.h"
+#include "video_core/engines/const_buffer_info.h"
+#include "video_core/engines/engine_upload.h"
#include "video_core/gpu.h"
#include "video_core/macro_interpreter.h"
-#include "video_core/memory_manager.h"
#include "video_core/textures/texture.h"
namespace Core {
class System;
}
+namespace Tegra {
+class MemoryManager;
+}
+
namespace VideoCore {
class RasterizerInterface;
}
namespace Tegra::Engines {
+/**
+ * This Engine is known as GF100_3D. Documentation can be found in:
+ * https://github.com/envytools/envytools/blob/master/rnndb/graph/gf100_3d.xml
+ * https://cgit.freedesktop.org/mesa/mesa/tree/src/gallium/drivers/nouveau/nvc0/nvc0_3d.xml.h
+ */
+
#define MAXWELL3D_REG_INDEX(field_name) \
(offsetof(Tegra::Engines::Maxwell3D::Regs, field_name) / sizeof(u32))
@@ -48,6 +60,7 @@ public:
static constexpr std::size_t NumCBData = 16;
static constexpr std::size_t NumVertexArrays = 32;
static constexpr std::size_t NumVertexAttributes = 32;
+ static constexpr std::size_t NumVaryings = 31;
static constexpr std::size_t NumTextureSamplers = 32;
static constexpr std::size_t NumClipDistances = 8;
static constexpr std::size_t MaxShaderProgram = 6;
@@ -240,9 +253,10 @@ public:
return "10_10_10_2";
case Size::Size_11_11_10:
return "11_11_10";
+ default:
+ UNREACHABLE();
+ return {};
}
- UNREACHABLE();
- return {};
}
std::string TypeString() const {
@@ -576,7 +590,28 @@ public:
u32 bind;
} macros;
- INSERT_PADDING_WORDS(0x188);
+ INSERT_PADDING_WORDS(0x17);
+
+ Upload::Registers upload;
+ struct {
+ union {
+ BitField<0, 1, u32> linear;
+ };
+ } exec_upload;
+
+ u32 data_upload;
+
+ INSERT_PADDING_WORDS(0x44);
+
+ struct {
+ union {
+ BitField<0, 16, u32> sync_point;
+ BitField<16, 1, u32> unknown;
+ BitField<20, 1, u32> increment;
+ };
+ } sync_info;
+
+ INSERT_PADDING_WORDS(0x11E);
u32 tfb_enabled;
@@ -1075,15 +1110,9 @@ public:
} regs{};
static_assert(sizeof(Regs) == Regs::NUM_REGS * sizeof(u32), "Maxwell3D Regs has wrong size");
+ static_assert(std::is_trivially_copyable_v<Regs>, "Maxwell3D Regs must be trivially copyable");
struct State {
- struct ConstBufferInfo {
- GPUVAddr address;
- u32 index;
- u32 size;
- bool enabled;
- };
-
struct ShaderStageInfo {
std::array<ConstBufferInfo, Regs::MaxConstBuffers> const_buffers;
};
@@ -1093,7 +1122,6 @@ public:
};
State state{};
- MemoryManager& memory_manager;
struct DirtyFlags {
std::bitset<8> color_buffer{0xFF};
@@ -1119,12 +1147,18 @@ public:
/// Write the value to the register identified by method.
void CallMethod(const GPU::MethodCall& method_call);
+ /// Given a Texture Handle, returns the TSC and TIC entries.
+ Texture::FullTextureInfo GetTextureInfo(const Texture::TextureHandle tex_handle,
+ std::size_t offset) const;
+
/// Returns a list of enabled textures for the specified shader stage.
std::vector<Texture::FullTextureInfo> GetStageTextures(Regs::ShaderStage stage) const;
/// Returns the texture information for a specific texture in a specific shader stage.
Texture::FullTextureInfo GetStageTexture(Regs::ShaderStage stage, std::size_t offset) const;
+ u32 AccessConstBuffer32(Regs::ShaderStage stage, u64 const_buffer, u64 offset) const;
+
/// Memory for macro code - it's undetermined how big this is, however 1MB is much larger than
/// we've seen used.
using MacroMemory = std::array<u32, 0x40000>;
@@ -1141,6 +1175,8 @@ private:
VideoCore::RasterizerInterface& rasterizer;
+ MemoryManager& memory_manager;
+
/// Start offsets of each macro in macro_memory
std::unordered_map<u32, u32> macro_offsets;
@@ -1155,6 +1191,8 @@ private:
/// Interpreter for the macro codes uploaded to the GPU.
MacroInterpreter macro_interpreter;
+ Upload::State upload_state;
+
/// Retrieves information about a specific TIC entry from the TIC buffer.
Texture::TICEntry GetTICEntry(u32 tic_index) const;
@@ -1180,6 +1218,9 @@ private:
/// Handles a write to the QUERY_GET register.
void ProcessQueryGet();
+ /// Handles writes to syncing register.
+ void ProcessSyncPoint();
+
/// Handles a write to the CB_DATA[i] register.
void ProcessCBData(u32 value);
@@ -1195,6 +1236,10 @@ private:
"Field " #field_name " has invalid position")
ASSERT_REG_POSITION(macros, 0x45);
+ASSERT_REG_POSITION(upload, 0x60);
+ASSERT_REG_POSITION(exec_upload, 0x6C);
+ASSERT_REG_POSITION(data_upload, 0x6D);
+ASSERT_REG_POSITION(sync_info, 0xB2);
ASSERT_REG_POSITION(tfb_enabled, 0x1D1);
ASSERT_REG_POSITION(rt, 0x200);
ASSERT_REG_POSITION(viewport_transform, 0x280);
diff --git a/src/video_core/engines/maxwell_dma.cpp b/src/video_core/engines/maxwell_dma.cpp
index 9dfea5999..3a5dfef0c 100644
--- a/src/video_core/engines/maxwell_dma.cpp
+++ b/src/video_core/engines/maxwell_dma.cpp
@@ -5,17 +5,18 @@
#include "common/assert.h"
#include "common/logging/log.h"
#include "core/core.h"
-#include "core/memory.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/engines/maxwell_dma.h"
+#include "video_core/memory_manager.h"
#include "video_core/rasterizer_interface.h"
+#include "video_core/renderer_base.h"
#include "video_core/textures/decoders.h"
namespace Tegra::Engines {
MaxwellDMA::MaxwellDMA(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
MemoryManager& memory_manager)
- : memory_manager(memory_manager), system{system}, rasterizer{rasterizer} {}
+ : system{system}, rasterizer{rasterizer}, memory_manager{memory_manager} {}
void MaxwellDMA::CallMethod(const GPU::MethodCall& method_call) {
ASSERT_MSG(method_call.method < Regs::NUM_REGS,
@@ -42,11 +43,6 @@ void MaxwellDMA::HandleCopy() {
const GPUVAddr source = regs.src_address.Address();
const GPUVAddr dest = regs.dst_address.Address();
- const auto source_cpu = memory_manager.GpuToCpuAddress(source);
- const auto dest_cpu = memory_manager.GpuToCpuAddress(dest);
- ASSERT_MSG(source_cpu, "Invalid source GPU address");
- ASSERT_MSG(dest_cpu, "Invalid destination GPU address");
-
// TODO(Subv): Perform more research and implement all features of this engine.
ASSERT(regs.exec.enable_swizzle == 0);
ASSERT(regs.exec.query_mode == Regs::QueryMode::None);
@@ -69,7 +65,7 @@ void MaxwellDMA::HandleCopy() {
// buffer of length `x_count`, otherwise we copy a 2D image of dimensions (x_count,
// y_count).
if (!regs.exec.enable_2d) {
- Memory::CopyBlock(*dest_cpu, *source_cpu, regs.x_count);
+ memory_manager.CopyBlock(dest, source, regs.x_count);
return;
}
@@ -78,53 +74,75 @@ void MaxwellDMA::HandleCopy() {
// rectangle. There is no need to manually flush/invalidate the regions because
// CopyBlock does that for us.
for (u32 line = 0; line < regs.y_count; ++line) {
- const VAddr source_line = *source_cpu + line * regs.src_pitch;
- const VAddr dest_line = *dest_cpu + line * regs.dst_pitch;
- Memory::CopyBlock(dest_line, source_line, regs.x_count);
+ const GPUVAddr source_line = source + line * regs.src_pitch;
+ const GPUVAddr dest_line = dest + line * regs.dst_pitch;
+ memory_manager.CopyBlock(dest_line, source_line, regs.x_count);
}
return;
}
ASSERT(regs.exec.enable_2d == 1);
- const std::size_t copy_size = regs.x_count * regs.y_count;
-
- const auto FlushAndInvalidate = [&](u32 src_size, u64 dst_size) {
- // TODO(Subv): For now, manually flush the regions until we implement GPU-accelerated
- // copying.
- Core::System::GetInstance().GPU().FlushRegion(*source_cpu, src_size);
-
- // We have to invalidate the destination region to evict any outdated surfaces from the
- // cache. We do this before actually writing the new data because the destination address
- // might contain a dirty surface that will have to be written back to memory.
- Core::System::GetInstance().GPU().InvalidateRegion(*dest_cpu, dst_size);
- };
-
if (regs.exec.is_dst_linear && !regs.exec.is_src_linear) {
ASSERT(regs.src_params.size_z == 1);
// If the input is tiled and the output is linear, deswizzle the input and copy it over.
-
const u32 src_bytes_per_pixel = regs.src_pitch / regs.src_params.size_x;
+ const std::size_t src_size = Texture::CalculateSize(
+ true, src_bytes_per_pixel, regs.src_params.size_x, regs.src_params.size_y,
+ regs.src_params.size_z, regs.src_params.BlockHeight(), regs.src_params.BlockDepth());
+
+ const std::size_t dst_size = regs.dst_pitch * regs.y_count;
+
+ if (read_buffer.size() < src_size) {
+ read_buffer.resize(src_size);
+ }
+
+ if (write_buffer.size() < dst_size) {
+ write_buffer.resize(dst_size);
+ }
- FlushAndInvalidate(regs.src_pitch * regs.src_params.size_y,
- copy_size * src_bytes_per_pixel);
+ memory_manager.ReadBlock(source, read_buffer.data(), src_size);
+ memory_manager.ReadBlock(dest, write_buffer.data(), dst_size);
Texture::UnswizzleSubrect(regs.x_count, regs.y_count, regs.dst_pitch,
- regs.src_params.size_x, src_bytes_per_pixel, *source_cpu,
- *dest_cpu, regs.src_params.BlockHeight(), regs.src_params.pos_x,
- regs.src_params.pos_y);
+ regs.src_params.size_x, src_bytes_per_pixel, read_buffer.data(),
+ write_buffer.data(), regs.src_params.BlockHeight(),
+ regs.src_params.pos_x, regs.src_params.pos_y);
+
+ memory_manager.WriteBlock(dest, write_buffer.data(), dst_size);
} else {
- ASSERT(regs.dst_params.size_z == 1);
- ASSERT(regs.src_pitch == regs.x_count);
+ ASSERT(regs.dst_params.BlockDepth() == 1);
+
+ const u32 src_bytes_per_pixel = regs.src_pitch / regs.x_count;
+
+ const std::size_t dst_size = Texture::CalculateSize(
+ true, src_bytes_per_pixel, regs.dst_params.size_x, regs.dst_params.size_y,
+ regs.dst_params.size_z, regs.dst_params.BlockHeight(), regs.dst_params.BlockDepth());
- const u32 src_bpp = regs.src_pitch / regs.x_count;
+ const std::size_t dst_layer_size = Texture::CalculateSize(
+ true, src_bytes_per_pixel, regs.dst_params.size_x, regs.dst_params.size_y, 1,
+ regs.dst_params.BlockHeight(), regs.dst_params.BlockDepth());
- FlushAndInvalidate(regs.src_pitch * regs.y_count,
- regs.dst_params.size_x * regs.dst_params.size_y * src_bpp);
+ const std::size_t src_size = regs.src_pitch * regs.y_count;
+
+ if (read_buffer.size() < src_size) {
+ read_buffer.resize(src_size);
+ }
+
+ if (write_buffer.size() < dst_size) {
+ write_buffer.resize(dst_size);
+ }
+
+ memory_manager.ReadBlock(source, read_buffer.data(), src_size);
+ memory_manager.ReadBlock(dest, write_buffer.data(), dst_size);
// If the input is linear and the output is tiled, swizzle the input and copy it over.
Texture::SwizzleSubrect(regs.x_count, regs.y_count, regs.src_pitch, regs.dst_params.size_x,
- src_bpp, *dest_cpu, *source_cpu, regs.dst_params.BlockHeight());
+ src_bytes_per_pixel,
+ write_buffer.data() + dst_layer_size * regs.dst_params.pos_z,
+ read_buffer.data(), regs.dst_params.BlockHeight());
+
+ memory_manager.WriteBlock(dest, write_buffer.data(), dst_size);
}
}
diff --git a/src/video_core/engines/maxwell_dma.h b/src/video_core/engines/maxwell_dma.h
index 34c369320..e5942f671 100644
--- a/src/video_core/engines/maxwell_dma.h
+++ b/src/video_core/engines/maxwell_dma.h
@@ -6,22 +6,31 @@
#include <array>
#include <cstddef>
+#include <vector>
#include "common/bit_field.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "video_core/gpu.h"
-#include "video_core/memory_manager.h"
namespace Core {
class System;
}
+namespace Tegra {
+class MemoryManager;
+}
+
namespace VideoCore {
class RasterizerInterface;
}
namespace Tegra::Engines {
+/**
+ * This Engine is known as GK104_Copy. Documentation can be found in:
+ * https://github.com/envytools/envytools/blob/master/rnndb/fifo/gk104_copy.xml
+ */
+
class MaxwellDMA final {
public:
explicit MaxwellDMA(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
@@ -60,6 +69,16 @@ public:
static_assert(sizeof(Parameters) == 24, "Parameters has wrong size");
+ enum class ComponentMode : u32 {
+ Src0 = 0,
+ Src1 = 1,
+ Src2 = 2,
+ Src3 = 3,
+ Const0 = 4,
+ Const1 = 5,
+ Zero = 6,
+ };
+
enum class CopyMode : u32 {
None = 0,
Unk1 = 1,
@@ -125,7 +144,26 @@ public:
u32 x_count;
u32 y_count;
- INSERT_PADDING_WORDS(0xBB);
+ INSERT_PADDING_WORDS(0xB8);
+
+ u32 const0;
+ u32 const1;
+ union {
+ BitField<0, 4, ComponentMode> component0;
+ BitField<4, 4, ComponentMode> component1;
+ BitField<8, 4, ComponentMode> component2;
+ BitField<12, 4, ComponentMode> component3;
+ BitField<16, 2, u32> component_size;
+ BitField<20, 3, u32> src_num_components;
+ BitField<24, 3, u32> dst_num_components;
+
+ u32 SrcBytePerPixel() const {
+ return src_num_components.Value() * component_size.Value();
+ }
+ u32 DstBytePerPixel() const {
+ return dst_num_components.Value() * component_size.Value();
+ }
+ } swizzle_config;
Parameters dst_params;
@@ -139,13 +177,16 @@ public:
};
} regs{};
- MemoryManager& memory_manager;
-
private:
Core::System& system;
VideoCore::RasterizerInterface& rasterizer;
+ MemoryManager& memory_manager;
+
+ std::vector<u8> read_buffer;
+ std::vector<u8> write_buffer;
+
/// Performs the copy from the source buffer to the destination buffer as configured in the
/// registers.
void HandleCopy();
@@ -162,6 +203,9 @@ ASSERT_REG_POSITION(src_pitch, 0x104);
ASSERT_REG_POSITION(dst_pitch, 0x105);
ASSERT_REG_POSITION(x_count, 0x106);
ASSERT_REG_POSITION(y_count, 0x107);
+ASSERT_REG_POSITION(const0, 0x1C0);
+ASSERT_REG_POSITION(const1, 0x1C1);
+ASSERT_REG_POSITION(swizzle_config, 0x1C2);
ASSERT_REG_POSITION(dst_params, 0x1C3);
ASSERT_REG_POSITION(src_params, 0x1CA);
diff --git a/src/video_core/engines/shader_bytecode.h b/src/video_core/engines/shader_bytecode.h
index c7eb15b6a..ffb3ec3e0 100644
--- a/src/video_core/engines/shader_bytecode.h
+++ b/src/video_core/engines/shader_bytecode.h
@@ -98,6 +98,10 @@ union Attribute {
BitField<22, 2, u64> element;
BitField<24, 6, Index> index;
BitField<47, 3, AttributeSize> size;
+
+ bool IsPhysical() const {
+ return element == 0 && static_cast<u64>(index.Value()) == 0;
+ }
} fmt20;
union {
@@ -324,11 +328,11 @@ enum class TextureQueryType : u64 {
enum class TextureProcessMode : u64 {
None = 0,
- LZ = 1, // Unknown, appears to be the same as none.
+ LZ = 1, // Load LOD of zero.
LB = 2, // Load Bias.
- LL = 3, // Load LOD (LevelOfDetail)
- LBA = 6, // Load Bias. The A is unknown, does not appear to differ with LB
- LLA = 7 // Load LOD. The A is unknown, does not appear to differ with LL
+ LL = 3, // Load LOD.
+ LBA = 6, // Load Bias. The A is unknown, does not appear to differ with LB.
+ LLA = 7 // Load LOD. The A is unknown, does not appear to differ with LL.
};
enum class TextureMiscMode : u64 {
@@ -387,6 +391,20 @@ enum class IpaSampleMode : u64 {
Offset = 2,
};
+enum class LmemLoadCacheManagement : u64 {
+ Default = 0,
+ LU = 1,
+ CI = 2,
+ CV = 3,
+};
+
+enum class LmemStoreCacheManagement : u64 {
+ Default = 0,
+ CG = 1,
+ CS = 2,
+ WT = 3,
+};
+
struct IpaMode {
IpaInterpMode interpolation_mode;
IpaSampleMode sampling_mode;
@@ -485,6 +503,11 @@ enum class SystemVariable : u64 {
CircularQueueEntryAddressHigh = 0x63,
};
+enum class PhysicalAttributeDirection : u64 {
+ Input = 0,
+ Output = 1,
+};
+
union Instruction {
Instruction& operator=(const Instruction& instr) {
value = instr.value;
@@ -507,6 +530,11 @@ union Instruction {
BitField<48, 16, u64> opcode;
union {
+ BitField<8, 8, Register> gpr;
+ BitField<20, 24, s64> offset;
+ } gmem;
+
+ union {
BitField<20, 16, u64> imm20_16;
BitField<20, 19, u64> imm20_19;
BitField<20, 32, s64> imm20_32;
@@ -573,6 +601,7 @@ union Instruction {
} alu;
union {
+ BitField<38, 1, u64> idx;
BitField<51, 1, u64> saturate;
BitField<52, 2, IpaSampleMode> sample_mode;
BitField<54, 2, IpaInterpMode> interp_mode;
@@ -782,16 +811,31 @@ union Instruction {
} ld_l;
union {
- BitField<44, 2, u64> unknown;
+ BitField<44, 2, LmemStoreCacheManagement> cache_management;
} st_l;
union {
BitField<48, 3, UniformType> type;
BitField<46, 2, u64> cache_mode;
- BitField<20, 24, s64> immediate_offset;
} ldg;
union {
+ BitField<48, 3, UniformType> type;
+ BitField<46, 2, u64> cache_mode;
+ } stg;
+
+ union {
+ BitField<32, 1, PhysicalAttributeDirection> direction;
+ BitField<47, 3, AttributeSize> size;
+ BitField<20, 11, u64> address;
+ } al2p;
+
+ union {
+ BitField<53, 3, UniformType> type;
+ BitField<52, 1, u64> extended;
+ } generic;
+
+ union {
BitField<0, 3, u64> pred0;
BitField<3, 3, u64> pred3;
BitField<7, 1, u64> abs_a;
@@ -917,21 +961,34 @@ union Instruction {
} iset;
union {
- BitField<8, 2, Register::Size> dest_size;
- BitField<10, 2, Register::Size> src_size;
- BitField<12, 1, u64> is_output_signed;
- BitField<13, 1, u64> is_input_signed;
- BitField<41, 2, u64> selector;
+ BitField<41, 2, u64> selector; // i2i and i2f only
BitField<45, 1, u64> negate_a;
BitField<49, 1, u64> abs_a;
+ BitField<10, 2, Register::Size> src_size;
+ BitField<13, 1, u64> is_input_signed;
+ BitField<8, 2, Register::Size> dst_size;
+ BitField<12, 1, u64> is_output_signed;
+
+ union {
+ BitField<39, 2, u64> tab5cb8_2;
+ } i2f;
union {
BitField<39, 2, F2iRoundingOp> rounding;
} f2i;
union {
- BitField<39, 4, F2fRoundingOp> rounding;
+ BitField<8, 2, Register::Size> src_size;
+ BitField<10, 2, Register::Size> dst_size;
+ 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 {
+ constexpr u64 rounding_mask = 0x0B;
+ return static_cast<F2fRoundingOp>(rounding.Value() & rounding_mask);
+ }
} f2f;
+
} conversion;
union {
@@ -967,6 +1024,38 @@ union Instruction {
} tex;
union {
+ BitField<28, 1, u64> array;
+ BitField<29, 2, TextureType> texture_type;
+ BitField<31, 4, u64> component_mask;
+ BitField<49, 1, u64> nodep_flag;
+ BitField<50, 1, u64> dc_flag;
+ BitField<36, 1, u64> aoffi_flag;
+ BitField<37, 3, TextureProcessMode> process_mode;
+
+ bool IsComponentEnabled(std::size_t component) const {
+ return ((1ULL << component) & component_mask) != 0;
+ }
+
+ TextureProcessMode GetTextureProcessMode() const {
+ return process_mode;
+ }
+
+ bool UsesMiscMode(TextureMiscMode mode) const {
+ switch (mode) {
+ case TextureMiscMode::DC:
+ return dc_flag != 0;
+ case TextureMiscMode::NODEP:
+ return nodep_flag != 0;
+ case TextureMiscMode::AOFFI:
+ return aoffi_flag != 0;
+ default:
+ break;
+ }
+ return false;
+ }
+ } tex_b;
+
+ union {
BitField<22, 6, TextureQueryType> query_type;
BitField<31, 4, u64> component_mask;
BitField<49, 1, u64> nodep_flag;
@@ -1238,13 +1327,16 @@ union Instruction {
union {
BitField<20, 16, u64> imm20_16;
+ BitField<35, 1, u64> high_b_rr; // used on RR
BitField<36, 1, u64> product_shift_left;
BitField<37, 1, u64> merge_37;
BitField<48, 1, u64> sign_a;
BitField<49, 1, u64> sign_b;
+ BitField<50, 2, XmadMode> mode_cbf; // used by CR, RC
BitField<50, 3, XmadMode> mode;
BitField<52, 1, u64> high_b;
BitField<53, 1, u64> high_a;
+ BitField<55, 1, u64> product_shift_left_second; // used on CR
BitField<56, 1, u64> merge_56;
} xmad;
@@ -1303,13 +1395,18 @@ public:
LD_L,
LD_S,
LD_C,
+ LD, // Load from generic memory
+ LDG, // Load from global memory
ST_A,
ST_L,
ST_S,
- LDG, // Load from global memory
- STG, // Store in global memory
+ ST, // Store in generic memory
+ STG, // Store in global memory
+ AL2P, // Transforms attribute memory into physical memory
TEX,
+ TEX_B, // Texture Load Bindless
TXQ, // Texture Query
+ TXQ_B, // Texture Query Bindless
TEXS, // Texture Fetch with scalar/non-vec4 source/destinations
TLDS, // Texture Load with scalar/non-vec4 source/destinations
TLD4, // Texture Load 4
@@ -1445,6 +1542,7 @@ public:
Flow,
Synch,
Memory,
+ Texture,
FloatSet,
FloatSetPredicate,
IntegerSet,
@@ -1565,25 +1663,30 @@ private:
INST("111000100100----", Id::BRA, Type::Flow, "BRA"),
INST("1111000011111---", Id::SYNC, Type::Flow, "SYNC"),
INST("111000110100---", Id::BRK, Type::Flow, "BRK"),
+ INST("111000110000----", Id::EXIT, Type::Flow, "EXIT"),
INST("1111000011110---", Id::DEPBAR, Type::Synch, "DEPBAR"),
INST("1110111111011---", Id::LD_A, Type::Memory, "LD_A"),
INST("1110111101001---", Id::LD_S, Type::Memory, "LD_S"),
INST("1110111101000---", Id::LD_L, Type::Memory, "LD_L"),
INST("1110111110010---", Id::LD_C, Type::Memory, "LD_C"),
+ INST("100-------------", Id::LD, Type::Memory, "LD"),
+ INST("1110111011010---", Id::LDG, Type::Memory, "LDG"),
INST("1110111111110---", Id::ST_A, Type::Memory, "ST_A"),
INST("1110111101011---", Id::ST_S, Type::Memory, "ST_S"),
INST("1110111101010---", Id::ST_L, Type::Memory, "ST_L"),
- INST("1110111011010---", Id::LDG, Type::Memory, "LDG"),
+ INST("101-------------", Id::ST, Type::Memory, "ST"),
INST("1110111011011---", Id::STG, Type::Memory, "STG"),
- INST("110000----111---", Id::TEX, Type::Memory, "TEX"),
- INST("1101111101001---", Id::TXQ, Type::Memory, "TXQ"),
- INST("1101-00---------", Id::TEXS, Type::Memory, "TEXS"),
- INST("1101101---------", Id::TLDS, Type::Memory, "TLDS"),
- INST("110010----111---", Id::TLD4, Type::Memory, "TLD4"),
- INST("1101111100------", Id::TLD4S, Type::Memory, "TLD4S"),
- INST("110111110110----", Id::TMML_B, Type::Memory, "TMML_B"),
- INST("1101111101011---", Id::TMML, Type::Memory, "TMML"),
- INST("111000110000----", Id::EXIT, Type::Trivial, "EXIT"),
+ INST("1110111110100---", Id::AL2P, Type::Memory, "AL2P"),
+ INST("110000----111---", Id::TEX, Type::Texture, "TEX"),
+ INST("1101111010111---", Id::TEX_B, Type::Texture, "TEX_B"),
+ INST("1101111101001---", Id::TXQ, Type::Texture, "TXQ"),
+ INST("1101111101010---", Id::TXQ_B, Type::Texture, "TXQ_B"),
+ INST("1101-00---------", Id::TEXS, Type::Texture, "TEXS"),
+ INST("1101101---------", Id::TLDS, Type::Texture, "TLDS"),
+ INST("110010----111---", Id::TLD4, Type::Texture, "TLD4"),
+ INST("1101111100------", Id::TLD4S, Type::Texture, "TLD4S"),
+ INST("110111110110----", Id::TMML_B, Type::Texture, "TMML_B"),
+ INST("1101111101011---", Id::TMML, Type::Texture, "TMML"),
INST("11100000--------", Id::IPA, Type::Trivial, "IPA"),
INST("1111101111100---", Id::OUT_R, Type::Trivial, "OUT_R"),
INST("1110111111010---", Id::ISBERD, Type::Trivial, "ISBERD"),
@@ -1661,7 +1764,7 @@ private:
INST("0011011-11110---", Id::BFI_IMM_R, Type::Bfi, "BFI_IMM_R"),
INST("0100110001000---", Id::LOP_C, Type::ArithmeticInteger, "LOP_C"),
INST("0101110001000---", Id::LOP_R, Type::ArithmeticInteger, "LOP_R"),
- INST("0011100001000---", Id::LOP_IMM, Type::ArithmeticInteger, "LOP_IMM"),
+ INST("0011100-01000---", Id::LOP_IMM, Type::ArithmeticInteger, "LOP_IMM"),
INST("000001----------", Id::LOP32I, Type::ArithmeticIntegerImmediate, "LOP32I"),
INST("0000001---------", Id::LOP3_C, Type::ArithmeticInteger, "LOP3_C"),
INST("0101101111100---", Id::LOP3_R, Type::ArithmeticInteger, "LOP3_R"),
@@ -1674,7 +1777,7 @@ private:
INST("0011100-00101---", Id::SHR_IMM, Type::Shift, "SHR_IMM"),
INST("0100110011100---", Id::I2I_C, Type::Conversion, "I2I_C"),
INST("0101110011100---", Id::I2I_R, Type::Conversion, "I2I_R"),
- INST("01110001-1000---", Id::I2I_IMM, Type::Conversion, "I2I_IMM"),
+ INST("0011101-11100---", Id::I2I_IMM, Type::Conversion, "I2I_IMM"),
INST("0100110010111---", Id::I2F_C, Type::Conversion, "I2F_C"),
INST("0101110010111---", Id::I2F_R, Type::Conversion, "I2F_R"),
INST("0011100-10111---", Id::I2F_IMM, Type::Conversion, "I2F_IMM"),
diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp
index 08abf8ac9..52706505b 100644
--- a/src/video_core/gpu.cpp
+++ b/src/video_core/gpu.cpp
@@ -12,6 +12,7 @@
#include "video_core/engines/maxwell_3d.h"
#include "video_core/engines/maxwell_dma.h"
#include "video_core/gpu.h"
+#include "video_core/memory_manager.h"
#include "video_core/renderer_base.h"
namespace Tegra {
@@ -30,13 +31,13 @@ u32 FramebufferConfig::BytesPerPixel(PixelFormat format) {
GPU::GPU(Core::System& system, VideoCore::RendererBase& renderer) : renderer{renderer} {
auto& rasterizer{renderer.Rasterizer()};
- memory_manager = std::make_unique<Tegra::MemoryManager>();
+ memory_manager = std::make_unique<Tegra::MemoryManager>(rasterizer);
dma_pusher = std::make_unique<Tegra::DmaPusher>(*this);
maxwell_3d = std::make_unique<Engines::Maxwell3D>(system, rasterizer, *memory_manager);
fermi_2d = std::make_unique<Engines::Fermi2D>(rasterizer, *memory_manager);
- kepler_compute = std::make_unique<Engines::KeplerCompute>(*memory_manager);
+ kepler_compute = std::make_unique<Engines::KeplerCompute>(system, rasterizer, *memory_manager);
maxwell_dma = std::make_unique<Engines::MaxwellDMA>(system, rasterizer, *memory_manager);
- kepler_memory = std::make_unique<Engines::KeplerMemory>(system, rasterizer, *memory_manager);
+ kepler_memory = std::make_unique<Engines::KeplerMemory>(system, *memory_manager);
}
GPU::~GPU() = default;
@@ -274,7 +275,6 @@ void GPU::ProcessSemaphoreTriggerMethod() {
const auto op =
static_cast<GpuSemaphoreOperation>(regs.semaphore_trigger & semaphoreOperationMask);
if (op == GpuSemaphoreOperation::WriteLong) {
- auto address = memory_manager->GpuToCpuAddress(regs.smaphore_address.SmaphoreAddress());
struct Block {
u32 sequence;
u32 zeros = 0;
@@ -286,11 +286,10 @@ void GPU::ProcessSemaphoreTriggerMethod() {
// TODO(Kmather73): Generate a real GPU timestamp and write it here instead of
// CoreTiming
block.timestamp = Core::System::GetInstance().CoreTiming().GetTicks();
- Memory::WriteBlock(*address, &block, sizeof(block));
+ memory_manager->WriteBlock(regs.semaphore_address.SemaphoreAddress(), &block,
+ sizeof(block));
} else {
- const auto address =
- memory_manager->GpuToCpuAddress(regs.smaphore_address.SmaphoreAddress());
- const u32 word = Memory::Read32(*address);
+ const u32 word{memory_manager->Read<u32>(regs.semaphore_address.SemaphoreAddress())};
if ((op == GpuSemaphoreOperation::AcquireEqual && word == regs.semaphore_sequence) ||
(op == GpuSemaphoreOperation::AcquireGequal &&
static_cast<s32>(word - regs.semaphore_sequence) > 0) ||
@@ -317,13 +316,11 @@ void GPU::ProcessSemaphoreTriggerMethod() {
}
void GPU::ProcessSemaphoreRelease() {
- const auto address = memory_manager->GpuToCpuAddress(regs.smaphore_address.SmaphoreAddress());
- Memory::Write32(*address, regs.semaphore_release);
+ memory_manager->Write<u32>(regs.semaphore_address.SemaphoreAddress(), regs.semaphore_release);
}
void GPU::ProcessSemaphoreAcquire() {
- const auto address = memory_manager->GpuToCpuAddress(regs.smaphore_address.SmaphoreAddress());
- const u32 word = Memory::Read32(*address);
+ const u32 word = memory_manager->Read<u32>(regs.semaphore_address.SemaphoreAddress());
const auto value = regs.semaphore_acquire;
if (word != value) {
regs.acquire_active = true;
diff --git a/src/video_core/gpu.h b/src/video_core/gpu.h
index 14a421cc1..fe6628923 100644
--- a/src/video_core/gpu.h
+++ b/src/video_core/gpu.h
@@ -9,7 +9,11 @@
#include "common/common_types.h"
#include "core/hle/service/nvflinger/buffer_queue.h"
#include "video_core/dma_pusher.h"
-#include "video_core/memory_manager.h"
+
+using CacheAddr = std::uintptr_t;
+inline CacheAddr ToCacheAddr(const void* host_ptr) {
+ return reinterpret_cast<CacheAddr>(host_ptr);
+}
namespace Core {
class System;
@@ -119,11 +123,13 @@ enum class EngineID {
MAXWELL_DMA_COPY_A = 0xB0B5,
};
+class MemoryManager;
+
class GPU {
public:
explicit GPU(Core::System& system, VideoCore::RendererBase& renderer);
- ~GPU();
+ virtual ~GPU();
struct MethodCall {
u32 method{};
@@ -171,11 +177,11 @@ public:
u32 address_high;
u32 address_low;
- GPUVAddr SmaphoreAddress() const {
+ GPUVAddr SemaphoreAddress() const {
return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) |
address_low);
}
- } smaphore_address;
+ } semaphore_address;
u32 semaphore_sequence;
u32 semaphore_trigger;
@@ -201,6 +207,11 @@ public:
};
} regs{};
+ /// 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;
+
/// Push GPU command entries to be processed
virtual void PushGPUEntries(Tegra::CommandList&& entries) = 0;
@@ -209,13 +220,13 @@ public:
std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer) = 0;
/// Notify rasterizer that any caches of the specified region should be flushed to Switch memory
- virtual void FlushRegion(VAddr addr, u64 size) = 0;
+ virtual void FlushRegion(CacheAddr addr, u64 size) = 0;
/// Notify rasterizer that any caches of the specified region should be invalidated
- virtual void InvalidateRegion(VAddr addr, u64 size) = 0;
+ virtual void InvalidateRegion(CacheAddr addr, u64 size) = 0;
/// Notify rasterizer that any caches of the specified region should be flushed and invalidated
- virtual void FlushAndInvalidateRegion(VAddr addr, u64 size) = 0;
+ virtual void FlushAndInvalidateRegion(CacheAddr addr, u64 size) = 0;
private:
void ProcessBindMethod(const MethodCall& method_call);
@@ -239,9 +250,8 @@ protected:
private:
std::unique_ptr<Tegra::MemoryManager> memory_manager;
- /// Mapping of command subchannels to their bound engine ids.
+ /// Mapping of command subchannels to their bound engine ids
std::array<EngineID, 8> bound_engines = {};
-
/// 3D engine
std::unique_ptr<Engines::Maxwell3D> maxwell_3d;
/// 2D engine
@@ -258,7 +268,7 @@ private:
static_assert(offsetof(GPU::Regs, field_name) == position * 4, \
"Field " #field_name " has invalid position")
-ASSERT_REG_POSITION(smaphore_address, 0x4);
+ASSERT_REG_POSITION(semaphore_address, 0x4);
ASSERT_REG_POSITION(semaphore_sequence, 0x6);
ASSERT_REG_POSITION(semaphore_trigger, 0x7);
ASSERT_REG_POSITION(reference_count, 0x14);
diff --git a/src/video_core/gpu_asynch.cpp b/src/video_core/gpu_asynch.cpp
index ad0a747e3..d4e2553a9 100644
--- a/src/video_core/gpu_asynch.cpp
+++ b/src/video_core/gpu_asynch.cpp
@@ -9,10 +9,14 @@
namespace VideoCommon {
GPUAsynch::GPUAsynch(Core::System& system, VideoCore::RendererBase& renderer)
- : Tegra::GPU(system, renderer), gpu_thread{renderer, *dma_pusher} {}
+ : GPU(system, renderer), gpu_thread{system} {}
GPUAsynch::~GPUAsynch() = default;
+void GPUAsynch::Start() {
+ gpu_thread.StartThread(renderer, *dma_pusher);
+}
+
void GPUAsynch::PushGPUEntries(Tegra::CommandList&& entries) {
gpu_thread.SubmitList(std::move(entries));
}
@@ -22,15 +26,15 @@ void GPUAsynch::SwapBuffers(
gpu_thread.SwapBuffers(std::move(framebuffer));
}
-void GPUAsynch::FlushRegion(VAddr addr, u64 size) {
+void GPUAsynch::FlushRegion(CacheAddr addr, u64 size) {
gpu_thread.FlushRegion(addr, size);
}
-void GPUAsynch::InvalidateRegion(VAddr addr, u64 size) {
+void GPUAsynch::InvalidateRegion(CacheAddr addr, u64 size) {
gpu_thread.InvalidateRegion(addr, size);
}
-void GPUAsynch::FlushAndInvalidateRegion(VAddr addr, u64 size) {
+void GPUAsynch::FlushAndInvalidateRegion(CacheAddr addr, u64 size) {
gpu_thread.FlushAndInvalidateRegion(addr, size);
}
diff --git a/src/video_core/gpu_asynch.h b/src/video_core/gpu_asynch.h
index 58046f3e9..30be74cba 100644
--- a/src/video_core/gpu_asynch.h
+++ b/src/video_core/gpu_asynch.h
@@ -13,22 +13,19 @@ class RendererBase;
namespace VideoCommon {
-namespace GPUThread {
-class ThreadManager;
-} // namespace GPUThread
-
/// Implementation of GPU interface that runs the GPU asynchronously
class GPUAsynch : public Tegra::GPU {
public:
explicit GPUAsynch(Core::System& system, VideoCore::RendererBase& renderer);
- ~GPUAsynch();
+ ~GPUAsynch() override;
+ void Start() override;
void PushGPUEntries(Tegra::CommandList&& entries) override;
void SwapBuffers(
std::optional<std::reference_wrapper<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 FlushRegion(CacheAddr addr, u64 size) override;
+ void InvalidateRegion(CacheAddr addr, u64 size) override;
+ void FlushAndInvalidateRegion(CacheAddr addr, u64 size) override;
private:
GPUThread::ThreadManager gpu_thread;
diff --git a/src/video_core/gpu_synch.cpp b/src/video_core/gpu_synch.cpp
index 4c00b96c7..45e43b1dc 100644
--- a/src/video_core/gpu_synch.cpp
+++ b/src/video_core/gpu_synch.cpp
@@ -8,10 +8,12 @@
namespace VideoCommon {
GPUSynch::GPUSynch(Core::System& system, VideoCore::RendererBase& renderer)
- : Tegra::GPU(system, renderer) {}
+ : GPU(system, renderer) {}
GPUSynch::~GPUSynch() = default;
+void GPUSynch::Start() {}
+
void GPUSynch::PushGPUEntries(Tegra::CommandList&& entries) {
dma_pusher->Push(std::move(entries));
dma_pusher->DispatchCalls();
@@ -22,15 +24,15 @@ void GPUSynch::SwapBuffers(
renderer.SwapBuffers(std::move(framebuffer));
}
-void GPUSynch::FlushRegion(VAddr addr, u64 size) {
+void GPUSynch::FlushRegion(CacheAddr addr, u64 size) {
renderer.Rasterizer().FlushRegion(addr, size);
}
-void GPUSynch::InvalidateRegion(VAddr addr, u64 size) {
+void GPUSynch::InvalidateRegion(CacheAddr addr, u64 size) {
renderer.Rasterizer().InvalidateRegion(addr, size);
}
-void GPUSynch::FlushAndInvalidateRegion(VAddr addr, u64 size) {
+void GPUSynch::FlushAndInvalidateRegion(CacheAddr addr, u64 size) {
renderer.Rasterizer().FlushAndInvalidateRegion(addr, size);
}
diff --git a/src/video_core/gpu_synch.h b/src/video_core/gpu_synch.h
index 658f683e2..3031fcf72 100644
--- a/src/video_core/gpu_synch.h
+++ b/src/video_core/gpu_synch.h
@@ -16,14 +16,15 @@ namespace VideoCommon {
class GPUSynch : public Tegra::GPU {
public:
explicit GPUSynch(Core::System& system, VideoCore::RendererBase& renderer);
- ~GPUSynch();
+ ~GPUSynch() override;
+ void Start() override;
void PushGPUEntries(Tegra::CommandList&& entries) override;
void SwapBuffers(
std::optional<std::reference_wrapper<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 FlushRegion(CacheAddr addr, u64 size) override;
+ void InvalidateRegion(CacheAddr addr, u64 size) override;
+ void FlushAndInvalidateRegion(CacheAddr addr, u64 size) override;
};
} // namespace VideoCommon
diff --git a/src/video_core/gpu_thread.cpp b/src/video_core/gpu_thread.cpp
index c5bdd2a17..3f0939ec9 100644
--- a/src/video_core/gpu_thread.cpp
+++ b/src/video_core/gpu_thread.cpp
@@ -4,8 +4,10 @@
#include "common/assert.h"
#include "common/microprofile.h"
+#include "core/core.h"
+#include "core/core_timing.h"
+#include "core/core_timing_util.h"
#include "core/frontend/scope_acquire_window_context.h"
-#include "core/settings.h"
#include "video_core/dma_pusher.h"
#include "video_core/gpu.h"
#include "video_core/gpu_thread.h"
@@ -13,38 +15,13 @@
namespace VideoCommon::GPUThread {
-/// Executes a single GPU thread command
-static void ExecuteCommand(CommandData* command, VideoCore::RendererBase& renderer,
- Tegra::DmaPusher& dma_pusher) {
- if (const auto submit_list = std::get_if<SubmitListCommand>(command)) {
- dma_pusher.Push(std::move(submit_list->entries));
- dma_pusher.DispatchCalls();
- } else if (const auto data = std::get_if<SwapBuffersCommand>(command)) {
- renderer.SwapBuffers(data->framebuffer);
- } else if (const auto data = std::get_if<FlushRegionCommand>(command)) {
- renderer.Rasterizer().FlushRegion(data->addr, data->size);
- } else if (const auto data = std::get_if<InvalidateRegionCommand>(command)) {
- renderer.Rasterizer().InvalidateRegion(data->addr, data->size);
- } else if (const auto data = std::get_if<FlushAndInvalidateRegionCommand>(command)) {
- renderer.Rasterizer().FlushAndInvalidateRegion(data->addr, data->size);
- } else {
- UNREACHABLE();
- }
-}
-
/// Runs the GPU thread
static void RunThread(VideoCore::RendererBase& renderer, Tegra::DmaPusher& dma_pusher,
SynchState& state) {
-
MicroProfileOnThreadCreate("GpuThread");
- auto WaitForWakeup = [&]() {
- std::unique_lock<std::mutex> lock{state.signal_mutex};
- state.signal_condition.wait(lock, [&] { return !state.is_idle || !state.is_running; });
- };
-
// Wait for first GPU command before acquiring the window context
- WaitForWakeup();
+ state.WaitForCommands();
// If emulation was stopped during disk shader loading, abort before trying to acquire context
if (!state.is_running) {
@@ -53,99 +30,96 @@ static void RunThread(VideoCore::RendererBase& renderer, Tegra::DmaPusher& dma_p
Core::Frontend::ScopeAcquireWindowContext acquire_context{renderer.GetRenderWindow()};
+ CommandDataContainer next;
while (state.is_running) {
- if (!state.is_running) {
- return;
- }
-
- {
- // Thread has been woken up, so make the previous write queue the next read queue
- std::lock_guard<std::mutex> lock{state.signal_mutex};
- std::swap(state.push_queue, state.pop_queue);
+ state.WaitForCommands();
+ while (!state.queue.Empty()) {
+ state.queue.Pop(next);
+ if (const 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)) {
+ renderer.SwapBuffers(std::move(data->framebuffer));
+ } 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().InvalidateRegion(data->addr, data->size);
+ } else if (std::holds_alternative<EndProcessingCommand>(next.data)) {
+ return;
+ } else {
+ UNREACHABLE();
+ }
+ state.signaled_fence = next.fence;
+ state.TrySynchronize();
}
-
- // Execute all of the GPU commands
- while (!state.pop_queue->empty()) {
- ExecuteCommand(&state.pop_queue->front(), renderer, dma_pusher);
- state.pop_queue->pop();
- }
-
- state.UpdateIdleState();
-
- // Signal that the GPU thread has finished processing commands
- if (state.is_idle) {
- state.idle_condition.notify_one();
- }
-
- // Wait for CPU thread to send more GPU commands
- WaitForWakeup();
}
}
-ThreadManager::ThreadManager(VideoCore::RendererBase& renderer, Tegra::DmaPusher& dma_pusher)
- : renderer{renderer}, dma_pusher{dma_pusher}, thread{RunThread, std::ref(renderer),
- std::ref(dma_pusher), std::ref(state)},
- thread_id{thread.get_id()} {}
+ThreadManager::ThreadManager(Core::System& system) : system{system} {}
ThreadManager::~ThreadManager() {
- {
- // Notify GPU thread that a shutdown is pending
- std::lock_guard<std::mutex> lock{state.signal_mutex};
- state.is_running = false;
+ if (!thread.joinable()) {
+ return;
}
- state.signal_condition.notify_one();
+ // Notify GPU thread that a shutdown is pending
+ PushCommand(EndProcessingCommand());
thread.join();
}
-void ThreadManager::SubmitList(Tegra::CommandList&& entries) {
- if (entries.empty()) {
- return;
- }
+void ThreadManager::StartThread(VideoCore::RendererBase& renderer, Tegra::DmaPusher& dma_pusher) {
+ thread = std::thread{RunThread, std::ref(renderer), std::ref(dma_pusher), std::ref(state)};
+ synchronization_event = system.CoreTiming().RegisterEvent(
+ "GPUThreadSynch", [this](u64 fence, s64) { state.WaitForSynchronization(fence); });
+}
- PushCommand(SubmitListCommand(std::move(entries)), false, false);
+void ThreadManager::SubmitList(Tegra::CommandList&& entries) {
+ const u64 fence{PushCommand(SubmitListCommand(std::move(entries)))};
+ const s64 synchronization_ticks{Core::Timing::usToCycles(std::chrono::microseconds{9000})};
+ system.CoreTiming().ScheduleEvent(synchronization_ticks, synchronization_event, fence);
}
void ThreadManager::SwapBuffers(
std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer) {
- PushCommand(SwapBuffersCommand(std::move(framebuffer)), true, false);
+ PushCommand(SwapBuffersCommand(std::move(framebuffer)));
}
-void ThreadManager::FlushRegion(VAddr addr, u64 size) {
- // Block the CPU when using accurate emulation
- PushCommand(FlushRegionCommand(addr, size), Settings::values.use_accurate_gpu_emulation, false);
+void ThreadManager::FlushRegion(CacheAddr addr, u64 size) {
+ PushCommand(FlushRegionCommand(addr, size));
}
-void ThreadManager::InvalidateRegion(VAddr addr, u64 size) {
- PushCommand(InvalidateRegionCommand(addr, size), true, true);
+void ThreadManager::InvalidateRegion(CacheAddr addr, u64 size) {
+ if (state.queue.Empty()) {
+ // It's quicker to invalidate a single region on the CPU if the queue is already empty
+ system.Renderer().Rasterizer().InvalidateRegion(addr, size);
+ } else {
+ PushCommand(InvalidateRegionCommand(addr, size));
+ }
}
-void ThreadManager::FlushAndInvalidateRegion(VAddr addr, u64 size) {
+void ThreadManager::FlushAndInvalidateRegion(CacheAddr addr, u64 size) {
+ // Skip flush on asynch mode, as FlushAndInvalidateRegion is not used for anything too important
InvalidateRegion(addr, size);
}
-void ThreadManager::PushCommand(CommandData&& command_data, bool wait_for_idle, bool allow_on_cpu) {
- {
- std::lock_guard<std::mutex> lock{state.signal_mutex};
-
- if ((allow_on_cpu && state.is_idle) || IsGpuThread()) {
- // Execute the command synchronously on the current thread
- ExecuteCommand(&command_data, renderer, dma_pusher);
- return;
- }
+u64 ThreadManager::PushCommand(CommandData&& command_data) {
+ const u64 fence{++state.last_fence};
+ state.queue.Push(CommandDataContainer(std::move(command_data), fence));
+ state.SignalCommands();
+ return fence;
+}
- // Push the command to the GPU thread
- state.UpdateIdleState();
- state.push_queue->emplace(command_data);
+MICROPROFILE_DEFINE(GPU_wait, "GPU", "Wait for the GPU", MP_RGB(128, 128, 192));
+void SynchState::WaitForSynchronization(u64 fence) {
+ if (signaled_fence >= fence) {
+ return;
}
- // Signal the GPU thread that commands are pending
- state.signal_condition.notify_one();
-
- if (wait_for_idle) {
- // Wait for the GPU to be idle (all commands to be executed)
- std::unique_lock<std::mutex> lock{state.idle_mutex};
- state.idle_condition.wait(lock, [this] { return static_cast<bool>(state.is_idle); });
+ // Wait for the GPU to be idle (all commands to be executed)
+ {
+ MICROPROFILE_SCOPE(GPU_wait);
+ std::unique_lock lock{synchronization_mutex};
+ synchronization_condition.wait(lock, [this, fence] { return signaled_fence >= fence; });
}
}
diff --git a/src/video_core/gpu_thread.h b/src/video_core/gpu_thread.h
index 2ad8214cc..05a168a72 100644
--- a/src/video_core/gpu_thread.h
+++ b/src/video_core/gpu_thread.h
@@ -4,26 +4,33 @@
#pragma once
-#include <array>
#include <atomic>
#include <condition_variable>
-#include <memory>
#include <mutex>
#include <optional>
#include <thread>
#include <variant>
+#include "common/threadsafe_queue.h"
+#include "video_core/gpu.h"
+
namespace Tegra {
struct FramebufferConfig;
class DmaPusher;
} // namespace Tegra
-namespace VideoCore {
-class RendererBase;
-} // namespace VideoCore
+namespace Core {
+class System;
+namespace Timing {
+struct EventType;
+} // namespace Timing
+} // namespace Core
namespace VideoCommon::GPUThread {
+/// Command to signal to the GPU thread that processing has ended
+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)} {}
@@ -36,67 +43,101 @@ struct SwapBuffersCommand final {
explicit SwapBuffersCommand(std::optional<const Tegra::FramebufferConfig> framebuffer)
: framebuffer{std::move(framebuffer)} {}
- std::optional<const Tegra::FramebufferConfig> 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(CacheAddr addr, u64 size) : addr{addr}, size{size} {}
- const VAddr addr;
- const u64 size;
+ CacheAddr addr;
+ u64 size;
};
/// 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(CacheAddr addr, u64 size) : addr{addr}, size{size} {}
- const VAddr addr;
- const u64 size;
+ CacheAddr addr;
+ u64 size;
};
/// Command to signal to the GPU thread to flush and invalidate a region
struct FlushAndInvalidateRegionCommand final {
- explicit constexpr FlushAndInvalidateRegionCommand(VAddr addr, u64 size)
+ explicit constexpr FlushAndInvalidateRegionCommand(CacheAddr addr, u64 size)
: addr{addr}, size{size} {}
- const VAddr addr;
- const u64 size;
+ CacheAddr addr;
+ u64 size;
};
-using CommandData = std::variant<SubmitListCommand, SwapBuffersCommand, FlushRegionCommand,
- InvalidateRegionCommand, FlushAndInvalidateRegionCommand>;
+using CommandData =
+ std::variant<EndProcessingCommand, SubmitListCommand, SwapBuffersCommand, FlushRegionCommand,
+ InvalidateRegionCommand, FlushAndInvalidateRegionCommand>;
+
+struct CommandDataContainer {
+ CommandDataContainer() = default;
+
+ CommandDataContainer(CommandData&& data, u64 next_fence)
+ : data{std::move(data)}, fence{next_fence} {}
+
+ CommandData data;
+ u64 fence{};
+};
/// Struct used to synchronize the GPU thread
struct SynchState final {
- std::atomic<bool> is_running{true};
- std::atomic<bool> is_idle{true};
- std::condition_variable signal_condition;
- std::mutex signal_mutex;
- std::condition_variable idle_condition;
- std::mutex idle_mutex;
-
- // We use two queues for sending commands to the GPU thread, one for writing (push_queue) to and
- // one for reading from (pop_queue). These are swapped whenever the current pop_queue becomes
- // empty. This allows for efficient thread-safe access, as it does not require any copies.
-
- using CommandQueue = std::queue<CommandData>;
- std::array<CommandQueue, 2> command_queues;
- CommandQueue* push_queue{&command_queues[0]};
- CommandQueue* pop_queue{&command_queues[1]};
-
- void UpdateIdleState() {
- std::lock_guard<std::mutex> lock{idle_mutex};
- is_idle = command_queues[0].empty() && command_queues[1].empty();
+ std::atomic_bool is_running{true};
+ std::atomic_int queued_frame_count{};
+ std::mutex synchronization_mutex;
+ std::mutex commands_mutex;
+ std::condition_variable commands_condition;
+ std::condition_variable synchronization_condition;
+
+ /// Returns true if the gap in GPU commands is small enough that we can consider the CPU and GPU
+ /// synchronized. This is entirely empirical.
+ bool IsSynchronized() const {
+ constexpr std::size_t max_queue_gap{5};
+ return queue.Size() <= max_queue_gap;
+ }
+
+ void TrySynchronize() {
+ if (IsSynchronized()) {
+ std::lock_guard lock{synchronization_mutex};
+ synchronization_condition.notify_one();
+ }
}
+
+ void WaitForSynchronization(u64 fence);
+
+ void SignalCommands() {
+ if (queue.Empty()) {
+ return;
+ }
+
+ commands_condition.notify_one();
+ }
+
+ void WaitForCommands() {
+ std::unique_lock lock{commands_mutex};
+ commands_condition.wait(lock, [this] { return !queue.Empty(); });
+ }
+
+ using CommandQueue = Common::SPSCQueue<CommandDataContainer>;
+ CommandQueue queue;
+ u64 last_fence{};
+ std::atomic<u64> signaled_fence{};
};
/// Class used to manage the GPU thread
class ThreadManager final {
public:
- explicit ThreadManager(VideoCore::RendererBase& renderer, Tegra::DmaPusher& dma_pusher);
+ explicit ThreadManager(Core::System& system);
~ThreadManager();
+ /// Creates and starts the GPU thread.
+ void StartThread(VideoCore::RendererBase& renderer, Tegra::DmaPusher& dma_pusher);
+
/// Push GPU command entries to be processed
void SubmitList(Tegra::CommandList&& entries);
@@ -105,32 +146,24 @@ public:
std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer);
/// Notify rasterizer that any caches of the specified region should be flushed to Switch memory
- void FlushRegion(VAddr addr, u64 size);
+ void FlushRegion(CacheAddr addr, u64 size);
/// Notify rasterizer that any caches of the specified region should be invalidated
- void InvalidateRegion(VAddr addr, u64 size);
+ void InvalidateRegion(CacheAddr addr, u64 size);
/// Notify rasterizer that any caches of the specified region should be flushed and invalidated
- void FlushAndInvalidateRegion(VAddr addr, u64 size);
-
- /// Waits the caller until the GPU thread is idle, used for synchronization
- void WaitForIdle();
+ void FlushAndInvalidateRegion(CacheAddr addr, u64 size);
private:
/// Pushes a command to be executed by the GPU thread
- void PushCommand(CommandData&& command_data, bool wait_for_idle, bool allow_on_cpu);
-
- /// Returns true if this is called by the GPU thread
- bool IsGpuThread() const {
- return std::this_thread::get_id() == thread_id;
- }
+ u64 PushCommand(CommandData&& command_data);
private:
SynchState state;
+ Core::System& system;
+ Core::Timing::EventType* synchronization_event{};
std::thread thread;
std::thread::id thread_id;
- VideoCore::RendererBase& renderer;
- Tegra::DmaPusher& dma_pusher;
};
} // namespace VideoCommon::GPUThread
diff --git a/src/video_core/macro_interpreter.cpp b/src/video_core/macro_interpreter.cpp
index 64f75db43..c766ed692 100644
--- a/src/video_core/macro_interpreter.cpp
+++ b/src/video_core/macro_interpreter.cpp
@@ -118,10 +118,12 @@ bool MacroInterpreter::Step(u32 offset, bool is_delay_slot) {
static_cast<u32>(opcode.operation.Value()));
}
+ // An instruction with the Exit flag will not actually
+ // cause an exit if it's executed inside a delay slot.
+ // TODO(Blinkhawk): Reversed to always exit. The behavior explained above requires further
+ // testing on the MME code.
if (opcode.is_exit) {
// Exit has a delay slot, execute the next instruction
- // Note: Executing an exit during a branch delay slot will cause the instruction at the
- // branch target to be executed before exiting.
Step(offset, true);
return false;
}
@@ -223,27 +225,21 @@ void MacroInterpreter::ProcessResult(ResultOperation operation, u32 reg, u32 res
}
u32 MacroInterpreter::FetchParameter() {
- ASSERT(next_parameter_index < parameters.size());
- return parameters[next_parameter_index++];
+ return parameters.at(next_parameter_index++);
}
u32 MacroInterpreter::GetRegister(u32 register_id) const {
- // Register 0 is supposed to always return 0.
- if (register_id == 0)
- return 0;
-
- ASSERT(register_id < registers.size());
- return registers[register_id];
+ return registers.at(register_id);
}
void MacroInterpreter::SetRegister(u32 register_id, u32 value) {
- // Register 0 is supposed to always return 0. NOP is implemented as a store to the zero
- // register.
- if (register_id == 0)
+ // Register 0 is hardwired as the zero register.
+ // Ensure no writes to it actually occur.
+ if (register_id == 0) {
return;
+ }
- ASSERT(register_id < registers.size());
- registers[register_id] = value;
+ registers.at(register_id) = value;
}
void MacroInterpreter::SetMethodAddress(u32 address) {
diff --git a/src/video_core/memory_manager.cpp b/src/video_core/memory_manager.cpp
index 54abe5298..5d8d126c1 100644
--- a/src/video_core/memory_manager.cpp
+++ b/src/video_core/memory_manager.cpp
@@ -5,181 +5,588 @@
#include "common/alignment.h"
#include "common/assert.h"
#include "common/logging/log.h"
+#include "core/memory.h"
#include "video_core/memory_manager.h"
+#include "video_core/rasterizer_interface.h"
namespace Tegra {
-MemoryManager::MemoryManager() {
- // Mark the first page as reserved, so that 0 is not a valid GPUVAddr. Otherwise, games might
- // try to use 0 as a valid address, which is also used to mean nullptr. This fixes a bug with
- // Undertale using 0 for a render target.
- PageSlot(0) = static_cast<u64>(PageStatus::Reserved);
+MemoryManager::MemoryManager(VideoCore::RasterizerInterface& rasterizer) : rasterizer{rasterizer} {
+ std::fill(page_table.pointers.begin(), page_table.pointers.end(), nullptr);
+ std::fill(page_table.attributes.begin(), page_table.attributes.end(),
+ Common::PageType::Unmapped);
+ page_table.Resize(address_space_width);
+
+ // Initialize the map with a single free region covering the entire managed space.
+ VirtualMemoryArea initial_vma;
+ initial_vma.size = address_space_end;
+ vma_map.emplace(initial_vma.base, initial_vma);
+
+ UpdatePageTableForVMA(initial_vma);
}
+MemoryManager::~MemoryManager() = default;
+
GPUVAddr MemoryManager::AllocateSpace(u64 size, u64 align) {
- const std::optional<GPUVAddr> gpu_addr{FindFreeBlock(0, size, align, PageStatus::Unmapped)};
+ const u64 aligned_size{Common::AlignUp(size, page_size)};
+ const GPUVAddr gpu_addr{FindFreeRegion(address_space_base, aligned_size)};
- ASSERT_MSG(gpu_addr, "unable to find available GPU memory");
+ AllocateMemory(gpu_addr, 0, aligned_size);
- for (u64 offset{}; offset < size; offset += PAGE_SIZE) {
- VAddr& slot{PageSlot(*gpu_addr + offset)};
+ return gpu_addr;
+}
- ASSERT(slot == static_cast<u64>(PageStatus::Unmapped));
+GPUVAddr MemoryManager::AllocateSpace(GPUVAddr gpu_addr, u64 size, u64 align) {
+ const u64 aligned_size{Common::AlignUp(size, page_size)};
- slot = static_cast<u64>(PageStatus::Allocated);
- }
+ AllocateMemory(gpu_addr, 0, aligned_size);
- return *gpu_addr;
+ return gpu_addr;
}
-GPUVAddr MemoryManager::AllocateSpace(GPUVAddr gpu_addr, u64 size, u64 align) {
- for (u64 offset{}; offset < size; offset += PAGE_SIZE) {
- VAddr& slot{PageSlot(gpu_addr + offset)};
+GPUVAddr MemoryManager::MapBufferEx(VAddr cpu_addr, u64 size) {
+ const u64 aligned_size{Common::AlignUp(size, page_size)};
+ const GPUVAddr gpu_addr{FindFreeRegion(address_space_base, aligned_size)};
- ASSERT(slot == static_cast<u64>(PageStatus::Unmapped));
+ MapBackingMemory(gpu_addr, Memory::GetPointer(cpu_addr), aligned_size, cpu_addr);
- slot = static_cast<u64>(PageStatus::Allocated);
- }
+ return gpu_addr;
+}
+
+GPUVAddr MemoryManager::MapBufferEx(VAddr cpu_addr, GPUVAddr gpu_addr, u64 size) {
+ ASSERT((gpu_addr & page_mask) == 0);
+
+ const u64 aligned_size{Common::AlignUp(size, page_size)};
+
+ MapBackingMemory(gpu_addr, Memory::GetPointer(cpu_addr), aligned_size, cpu_addr);
return gpu_addr;
}
-GPUVAddr MemoryManager::MapBufferEx(VAddr cpu_addr, u64 size) {
- const std::optional<GPUVAddr> gpu_addr{FindFreeBlock(0, size, PAGE_SIZE, PageStatus::Unmapped)};
+GPUVAddr MemoryManager::UnmapBuffer(GPUVAddr gpu_addr, u64 size) {
+ ASSERT((gpu_addr & page_mask) == 0);
- ASSERT_MSG(gpu_addr, "unable to find available GPU memory");
+ const u64 aligned_size{Common::AlignUp(size, page_size)};
+ const CacheAddr cache_addr{ToCacheAddr(GetPointer(gpu_addr))};
- for (u64 offset{}; offset < size; offset += PAGE_SIZE) {
- VAddr& slot{PageSlot(*gpu_addr + offset)};
+ rasterizer.FlushAndInvalidateRegion(cache_addr, aligned_size);
+ UnmapRange(gpu_addr, aligned_size);
- ASSERT(slot == static_cast<u64>(PageStatus::Unmapped));
+ return gpu_addr;
+}
+
+GPUVAddr MemoryManager::FindFreeRegion(GPUVAddr region_start, u64 size) const {
+ // Find the first Free VMA.
+ const VMAHandle vma_handle{
+ std::find_if(vma_map.begin(), vma_map.end(), [region_start, size](const auto& vma) {
+ if (vma.second.type != VirtualMemoryArea::Type::Unmapped) {
+ return false;
+ }
- slot = cpu_addr + offset;
+ const VAddr vma_end{vma.second.base + vma.second.size};
+ return vma_end > region_start && vma_end >= region_start + size;
+ })};
+
+ if (vma_handle == vma_map.end()) {
+ return {};
}
- const MappedRegion region{cpu_addr, *gpu_addr, size};
- mapped_regions.push_back(region);
+ return std::max(region_start, vma_handle->second.base);
+}
- return *gpu_addr;
+bool MemoryManager::IsAddressValid(GPUVAddr addr) const {
+ return (addr >> page_bits) < page_table.pointers.size();
}
-GPUVAddr MemoryManager::MapBufferEx(VAddr cpu_addr, GPUVAddr gpu_addr, u64 size) {
- ASSERT((gpu_addr & PAGE_MASK) == 0);
+std::optional<VAddr> MemoryManager::GpuToCpuAddress(GPUVAddr addr) const {
+ if (!IsAddressValid(addr)) {
+ return {};
+ }
+
+ const VAddr cpu_addr{page_table.backing_addr[addr >> page_bits]};
+ if (cpu_addr) {
+ return cpu_addr + (addr & page_mask);
+ }
- if (PageSlot(gpu_addr) != static_cast<u64>(PageStatus::Allocated)) {
- // Page has been already mapped. In this case, we must find a new area of memory to use that
- // is different than the specified one. Super Mario Odyssey hits this scenario when changing
- // areas, but we do not want to overwrite the old pages.
- // TODO(bunnei): We need to write a hardware test to confirm this behavior.
+ return {};
+}
- LOG_ERROR(HW_GPU, "attempting to map addr 0x{:016X}, which is not available!", gpu_addr);
+template <typename T>
+T MemoryManager::Read(GPUVAddr addr) const {
+ if (!IsAddressValid(addr)) {
+ return {};
+ }
- const std::optional<GPUVAddr> new_gpu_addr{
- FindFreeBlock(gpu_addr, size, PAGE_SIZE, PageStatus::Allocated)};
+ const u8* page_pointer{page_table.pointers[addr >> page_bits]};
+ if (page_pointer) {
+ // NOTE: Avoid adding any extra logic to this fast-path block
+ T value;
+ std::memcpy(&value, &page_pointer[addr & page_mask], sizeof(T));
+ return value;
+ }
- ASSERT_MSG(new_gpu_addr, "unable to find available GPU memory");
+ switch (page_table.attributes[addr >> page_bits]) {
+ case Common::PageType::Unmapped:
+ LOG_ERROR(HW_GPU, "Unmapped Read{} @ 0x{:08X}", sizeof(T) * 8, addr);
+ return 0;
+ case Common::PageType::Memory:
+ ASSERT_MSG(false, "Mapped memory page without a pointer @ {:016X}", addr);
+ break;
+ default:
+ UNREACHABLE();
+ }
+ return {};
+}
- gpu_addr = *new_gpu_addr;
+template <typename T>
+void MemoryManager::Write(GPUVAddr addr, T data) {
+ if (!IsAddressValid(addr)) {
+ return;
}
- for (u64 offset{}; offset < size; offset += PAGE_SIZE) {
- VAddr& slot{PageSlot(gpu_addr + offset)};
+ u8* page_pointer{page_table.pointers[addr >> page_bits]};
+ if (page_pointer) {
+ // NOTE: Avoid adding any extra logic to this fast-path block
+ std::memcpy(&page_pointer[addr & page_mask], &data, sizeof(T));
+ return;
+ }
- ASSERT(slot == static_cast<u64>(PageStatus::Allocated));
+ switch (page_table.attributes[addr >> page_bits]) {
+ case Common::PageType::Unmapped:
+ LOG_ERROR(HW_GPU, "Unmapped Write{} 0x{:08X} @ 0x{:016X}", sizeof(data) * 8,
+ static_cast<u32>(data), addr);
+ return;
+ case Common::PageType::Memory:
+ ASSERT_MSG(false, "Mapped memory page without a pointer @ {:016X}", addr);
+ break;
+ default:
+ UNREACHABLE();
+ }
+}
- slot = cpu_addr + offset;
+template u8 MemoryManager::Read<u8>(GPUVAddr addr) const;
+template u16 MemoryManager::Read<u16>(GPUVAddr addr) const;
+template u32 MemoryManager::Read<u32>(GPUVAddr addr) const;
+template u64 MemoryManager::Read<u64>(GPUVAddr addr) const;
+template void MemoryManager::Write<u8>(GPUVAddr addr, u8 data);
+template void MemoryManager::Write<u16>(GPUVAddr addr, u16 data);
+template void MemoryManager::Write<u32>(GPUVAddr addr, u32 data);
+template void MemoryManager::Write<u64>(GPUVAddr addr, u64 data);
+
+u8* MemoryManager::GetPointer(GPUVAddr addr) {
+ if (!IsAddressValid(addr)) {
+ return {};
}
- const MappedRegion region{cpu_addr, gpu_addr, size};
- mapped_regions.push_back(region);
+ u8* const page_pointer{page_table.pointers[addr >> page_bits]};
+ if (page_pointer != nullptr) {
+ return page_pointer + (addr & page_mask);
+ }
- return gpu_addr;
+ LOG_ERROR(HW_GPU, "Unknown GetPointer @ 0x{:016X}", addr);
+ return {};
}
-GPUVAddr MemoryManager::UnmapBuffer(GPUVAddr gpu_addr, u64 size) {
- ASSERT((gpu_addr & PAGE_MASK) == 0);
+const u8* MemoryManager::GetPointer(GPUVAddr addr) const {
+ if (!IsAddressValid(addr)) {
+ return {};
+ }
- for (u64 offset{}; offset < size; offset += PAGE_SIZE) {
- VAddr& slot{PageSlot(gpu_addr + offset)};
+ const u8* const page_pointer{page_table.pointers[addr >> page_bits]};
+ if (page_pointer != nullptr) {
+ return page_pointer + (addr & page_mask);
+ }
- ASSERT(slot != static_cast<u64>(PageStatus::Allocated) &&
- slot != static_cast<u64>(PageStatus::Unmapped));
+ LOG_ERROR(HW_GPU, "Unknown GetPointer @ 0x{:016X}", addr);
+ return {};
+}
- slot = static_cast<u64>(PageStatus::Unmapped);
+bool MemoryManager::IsBlockContinuous(const GPUVAddr start, const std::size_t size) const {
+ const GPUVAddr end = start + size;
+ const auto host_ptr_start = reinterpret_cast<std::uintptr_t>(GetPointer(start));
+ const auto host_ptr_end = reinterpret_cast<std::uintptr_t>(GetPointer(end));
+ const auto range = static_cast<std::size_t>(host_ptr_end - host_ptr_start);
+ return range == size;
+}
+
+void MemoryManager::ReadBlock(GPUVAddr src_addr, void* dest_buffer, const std::size_t size) const {
+ std::size_t remaining_size{size};
+ std::size_t page_index{src_addr >> page_bits};
+ std::size_t page_offset{src_addr & page_mask};
+
+ while (remaining_size > 0) {
+ const std::size_t copy_amount{
+ std::min(static_cast<std::size_t>(page_size) - page_offset, remaining_size)};
+
+ switch (page_table.attributes[page_index]) {
+ case Common::PageType::Memory: {
+ const u8* src_ptr{page_table.pointers[page_index] + page_offset};
+ rasterizer.FlushRegion(ToCacheAddr(src_ptr), copy_amount);
+ std::memcpy(dest_buffer, src_ptr, copy_amount);
+ break;
+ }
+ default:
+ UNREACHABLE();
+ }
+
+ page_index++;
+ page_offset = 0;
+ dest_buffer = static_cast<u8*>(dest_buffer) + copy_amount;
+ remaining_size -= copy_amount;
}
+}
- // Delete the region mappings that are contained within the unmapped region
- mapped_regions.erase(std::remove_if(mapped_regions.begin(), mapped_regions.end(),
- [&](const MappedRegion& region) {
- return region.gpu_addr <= gpu_addr &&
- region.gpu_addr + region.size < gpu_addr + size;
- }),
- mapped_regions.end());
- return gpu_addr;
+void MemoryManager::ReadBlockUnsafe(GPUVAddr src_addr, void* dest_buffer,
+ const std::size_t size) const {
+ std::size_t remaining_size{size};
+ std::size_t page_index{src_addr >> page_bits};
+ std::size_t page_offset{src_addr & page_mask};
+
+ while (remaining_size > 0) {
+ const std::size_t copy_amount{
+ std::min(static_cast<std::size_t>(page_size) - page_offset, remaining_size)};
+ const u8* page_pointer = page_table.pointers[page_index];
+ if (page_pointer) {
+ const u8* src_ptr{page_pointer + page_offset};
+ std::memcpy(dest_buffer, src_ptr, copy_amount);
+ } else {
+ std::memset(dest_buffer, 0, copy_amount);
+ }
+ page_index++;
+ page_offset = 0;
+ dest_buffer = static_cast<u8*>(dest_buffer) + copy_amount;
+ remaining_size -= copy_amount;
+ }
}
-GPUVAddr MemoryManager::GetRegionEnd(GPUVAddr region_start) const {
- for (const auto& region : mapped_regions) {
- const GPUVAddr region_end{region.gpu_addr + region.size};
- if (region_start >= region.gpu_addr && region_start < region_end) {
- return region_end;
+void MemoryManager::WriteBlock(GPUVAddr dest_addr, const void* src_buffer, const std::size_t size) {
+ std::size_t remaining_size{size};
+ std::size_t page_index{dest_addr >> page_bits};
+ std::size_t page_offset{dest_addr & page_mask};
+
+ while (remaining_size > 0) {
+ const std::size_t copy_amount{
+ std::min(static_cast<std::size_t>(page_size) - page_offset, remaining_size)};
+
+ switch (page_table.attributes[page_index]) {
+ case Common::PageType::Memory: {
+ u8* dest_ptr{page_table.pointers[page_index] + page_offset};
+ rasterizer.InvalidateRegion(ToCacheAddr(dest_ptr), copy_amount);
+ std::memcpy(dest_ptr, src_buffer, copy_amount);
+ break;
}
+ default:
+ UNREACHABLE();
+ }
+
+ page_index++;
+ page_offset = 0;
+ src_buffer = static_cast<const u8*>(src_buffer) + copy_amount;
+ remaining_size -= copy_amount;
}
- return {};
}
-std::optional<GPUVAddr> MemoryManager::FindFreeBlock(GPUVAddr region_start, u64 size, u64 align,
- PageStatus status) {
- GPUVAddr gpu_addr{region_start};
- u64 free_space{};
- align = (align + PAGE_MASK) & ~PAGE_MASK;
+void MemoryManager::WriteBlockUnsafe(GPUVAddr dest_addr, const void* src_buffer,
+ const std::size_t size) {
+ std::size_t remaining_size{size};
+ std::size_t page_index{dest_addr >> page_bits};
+ std::size_t page_offset{dest_addr & page_mask};
+
+ while (remaining_size > 0) {
+ const std::size_t copy_amount{
+ std::min(static_cast<std::size_t>(page_size) - page_offset, remaining_size)};
+ u8* page_pointer = page_table.pointers[page_index];
+ if (page_pointer) {
+ u8* dest_ptr{page_pointer + page_offset};
+ std::memcpy(dest_ptr, src_buffer, copy_amount);
+ }
+ page_index++;
+ page_offset = 0;
+ src_buffer = static_cast<const u8*>(src_buffer) + copy_amount;
+ remaining_size -= copy_amount;
+ }
+}
- while (gpu_addr + free_space < MAX_ADDRESS) {
- if (PageSlot(gpu_addr + free_space) == static_cast<u64>(status)) {
- free_space += PAGE_SIZE;
- if (free_space >= size) {
- return gpu_addr;
- }
- } else {
- gpu_addr += free_space + PAGE_SIZE;
- free_space = 0;
- gpu_addr = Common::AlignUp(gpu_addr, align);
+void MemoryManager::CopyBlock(GPUVAddr dest_addr, GPUVAddr src_addr, const std::size_t size) {
+ std::size_t remaining_size{size};
+ std::size_t page_index{src_addr >> page_bits};
+ std::size_t page_offset{src_addr & page_mask};
+
+ while (remaining_size > 0) {
+ const std::size_t copy_amount{
+ std::min(static_cast<std::size_t>(page_size) - page_offset, remaining_size)};
+
+ switch (page_table.attributes[page_index]) {
+ case Common::PageType::Memory: {
+ const u8* src_ptr{page_table.pointers[page_index] + page_offset};
+ rasterizer.FlushRegion(ToCacheAddr(src_ptr), copy_amount);
+ WriteBlock(dest_addr, src_ptr, copy_amount);
+ break;
}
+ default:
+ UNREACHABLE();
+ }
+
+ page_index++;
+ page_offset = 0;
+ dest_addr += static_cast<VAddr>(copy_amount);
+ src_addr += static_cast<VAddr>(copy_amount);
+ remaining_size -= copy_amount;
}
+}
- return {};
+void MemoryManager::CopyBlockUnsafe(GPUVAddr dest_addr, GPUVAddr src_addr, const std::size_t size) {
+ std::vector<u8> tmp_buffer(size);
+ ReadBlockUnsafe(src_addr, tmp_buffer.data(), size);
+ WriteBlockUnsafe(dest_addr, tmp_buffer.data(), size);
+}
+
+void MemoryManager::MapPages(GPUVAddr base, u64 size, u8* memory, Common::PageType type,
+ VAddr backing_addr) {
+ LOG_DEBUG(HW_GPU, "Mapping {} onto {:016X}-{:016X}", fmt::ptr(memory), base * page_size,
+ (base + size) * page_size);
+
+ const VAddr end{base + size};
+ ASSERT_MSG(end <= page_table.pointers.size(), "out of range mapping at {:016X}",
+ base + page_table.pointers.size());
+
+ std::fill(page_table.attributes.begin() + base, page_table.attributes.begin() + end, type);
+
+ if (memory == nullptr) {
+ std::fill(page_table.pointers.begin() + base, page_table.pointers.begin() + end, memory);
+ std::fill(page_table.backing_addr.begin() + base, page_table.backing_addr.begin() + end,
+ backing_addr);
+ } else {
+ while (base != end) {
+ page_table.pointers[base] = memory;
+ page_table.backing_addr[base] = backing_addr;
+
+ base += 1;
+ memory += page_size;
+ backing_addr += page_size;
+ }
+ }
+}
+
+void MemoryManager::MapMemoryRegion(GPUVAddr base, u64 size, u8* target, VAddr backing_addr) {
+ 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(base / page_size, size / page_size, target, Common::PageType::Memory, backing_addr);
+}
+
+void MemoryManager::UnmapRegion(GPUVAddr 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(base / page_size, size / page_size, nullptr, Common::PageType::Unmapped);
+}
+
+bool VirtualMemoryArea::CanBeMergedWith(const VirtualMemoryArea& next) const {
+ ASSERT(base + size == next.base);
+ if (type != next.type) {
+ return {};
+ }
+ if (type == VirtualMemoryArea::Type::Allocated && (offset + size != next.offset)) {
+ return {};
+ }
+ if (type == VirtualMemoryArea::Type::Mapped && backing_memory + size != next.backing_memory) {
+ return {};
+ }
+ return true;
+}
+
+MemoryManager::VMAHandle MemoryManager::FindVMA(GPUVAddr target) const {
+ if (target >= address_space_end) {
+ return vma_map.end();
+ } else {
+ return std::prev(vma_map.upper_bound(target));
+ }
+}
+
+MemoryManager::VMAIter MemoryManager::Allocate(VMAIter vma_handle) {
+ VirtualMemoryArea& vma{vma_handle->second};
+
+ vma.type = VirtualMemoryArea::Type::Allocated;
+ vma.backing_addr = 0;
+ vma.backing_memory = {};
+ UpdatePageTableForVMA(vma);
+
+ return MergeAdjacent(vma_handle);
+}
+
+MemoryManager::VMAHandle MemoryManager::AllocateMemory(GPUVAddr target, std::size_t offset,
+ u64 size) {
+
+ // This is the appropriately sized VMA that will turn into our allocation.
+ VMAIter vma_handle{CarveVMA(target, size)};
+ VirtualMemoryArea& vma{vma_handle->second};
+
+ ASSERT(vma.size == size);
+
+ vma.offset = offset;
+
+ return Allocate(vma_handle);
+}
+
+MemoryManager::VMAHandle MemoryManager::MapBackingMemory(GPUVAddr target, u8* memory, u64 size,
+ VAddr backing_addr) {
+ // This is the appropriately sized VMA that will turn into our allocation.
+ VMAIter vma_handle{CarveVMA(target, size)};
+ VirtualMemoryArea& vma{vma_handle->second};
+
+ ASSERT(vma.size == size);
+
+ vma.type = VirtualMemoryArea::Type::Mapped;
+ vma.backing_memory = memory;
+ vma.backing_addr = backing_addr;
+ UpdatePageTableForVMA(vma);
+
+ return MergeAdjacent(vma_handle);
+}
+
+void MemoryManager::UnmapRange(GPUVAddr target, u64 size) {
+ VMAIter vma{CarveVMARange(target, size)};
+ const VAddr target_end{target + size};
+ const VMAIter end{vma_map.end()};
+
+ // The comparison against the end of the range must be done using addresses since VMAs can be
+ // merged during this process, causing invalidation of the iterators.
+ while (vma != end && vma->second.base < target_end) {
+ // Unmapped ranges return to allocated state and can be reused
+ // This behavior is used by Super Mario Odyssey, Sonic Forces, and likely other games
+ vma = std::next(Allocate(vma));
+ }
+
+ ASSERT(FindVMA(target)->second.size >= size);
+}
+
+MemoryManager::VMAIter MemoryManager::StripIterConstness(const VMAHandle& iter) {
+ // This uses a neat C++ trick to convert a const_iterator to a regular iterator, given
+ // non-const access to its container.
+ return vma_map.erase(iter, iter); // Erases an empty range of elements
}
-std::optional<VAddr> MemoryManager::GpuToCpuAddress(GPUVAddr gpu_addr) {
- const VAddr base_addr{PageSlot(gpu_addr)};
+MemoryManager::VMAIter MemoryManager::CarveVMA(GPUVAddr base, u64 size) {
+ ASSERT_MSG((size & page_mask) == 0, "non-page aligned size: 0x{:016X}", size);
+ ASSERT_MSG((base & page_mask) == 0, "non-page aligned base: 0x{:016X}", base);
- if (base_addr == static_cast<u64>(PageStatus::Allocated) ||
- base_addr == static_cast<u64>(PageStatus::Unmapped) ||
- base_addr == static_cast<u64>(PageStatus::Reserved)) {
+ VMAIter vma_handle{StripIterConstness(FindVMA(base))};
+ if (vma_handle == vma_map.end()) {
+ // Target address is outside the managed range
return {};
}
- return base_addr + (gpu_addr & PAGE_MASK);
+ const VirtualMemoryArea& vma{vma_handle->second};
+ if (vma.type == VirtualMemoryArea::Type::Mapped) {
+ // Region is already allocated
+ return vma_handle;
+ }
+
+ const VAddr start_in_vma{base - vma.base};
+ const VAddr end_in_vma{start_in_vma + size};
+
+ ASSERT_MSG(end_in_vma <= vma.size, "region size 0x{:016X} is less than required size 0x{:016X}",
+ vma.size, end_in_vma);
+
+ if (end_in_vma < vma.size) {
+ // Split VMA at the end of the allocated region
+ SplitVMA(vma_handle, end_in_vma);
+ }
+ if (start_in_vma != 0) {
+ // Split VMA at the start of the allocated region
+ vma_handle = SplitVMA(vma_handle, start_in_vma);
+ }
+
+ return vma_handle;
}
-std::vector<GPUVAddr> MemoryManager::CpuToGpuAddress(VAddr cpu_addr) const {
- std::vector<GPUVAddr> results;
- for (const auto& region : mapped_regions) {
- if (cpu_addr >= region.cpu_addr && cpu_addr < (region.cpu_addr + region.size)) {
- const u64 offset{cpu_addr - region.cpu_addr};
- results.push_back(region.gpu_addr + offset);
+MemoryManager::VMAIter MemoryManager::CarveVMARange(GPUVAddr target, u64 size) {
+ ASSERT_MSG((size & page_mask) == 0, "non-page aligned size: 0x{:016X}", size);
+ ASSERT_MSG((target & page_mask) == 0, "non-page aligned base: 0x{:016X}", target);
+
+ const VAddr target_end{target + size};
+ ASSERT(target_end >= target);
+ ASSERT(size > 0);
+
+ VMAIter begin_vma{StripIterConstness(FindVMA(target))};
+ const VMAIter i_end{vma_map.lower_bound(target_end)};
+ if (std::any_of(begin_vma, i_end, [](const auto& entry) {
+ return entry.second.type == VirtualMemoryArea::Type::Unmapped;
+ })) {
+ return {};
+ }
+
+ if (target != begin_vma->second.base) {
+ begin_vma = SplitVMA(begin_vma, target - begin_vma->second.base);
+ }
+
+ VMAIter end_vma{StripIterConstness(FindVMA(target_end))};
+ if (end_vma != vma_map.end() && target_end != end_vma->second.base) {
+ end_vma = SplitVMA(end_vma, target_end - end_vma->second.base);
+ }
+
+ return begin_vma;
+}
+
+MemoryManager::VMAIter MemoryManager::SplitVMA(VMAIter vma_handle, u64 offset_in_vma) {
+ VirtualMemoryArea& old_vma{vma_handle->second};
+ VirtualMemoryArea new_vma{old_vma}; // Make a copy of the VMA
+
+ // For now, don't allow no-op VMA splits (trying to split at a boundary) because it's probably
+ // a bug. This restriction might be removed later.
+ ASSERT(offset_in_vma < old_vma.size);
+ ASSERT(offset_in_vma > 0);
+
+ old_vma.size = offset_in_vma;
+ new_vma.base += offset_in_vma;
+ new_vma.size -= offset_in_vma;
+
+ switch (new_vma.type) {
+ case VirtualMemoryArea::Type::Unmapped:
+ break;
+ case VirtualMemoryArea::Type::Allocated:
+ new_vma.offset += offset_in_vma;
+ break;
+ case VirtualMemoryArea::Type::Mapped:
+ new_vma.backing_memory += offset_in_vma;
+ break;
+ }
+
+ ASSERT(old_vma.CanBeMergedWith(new_vma));
+
+ return vma_map.emplace_hint(std::next(vma_handle), new_vma.base, new_vma);
+}
+
+MemoryManager::VMAIter MemoryManager::MergeAdjacent(VMAIter iter) {
+ const VMAIter next_vma{std::next(iter)};
+ if (next_vma != vma_map.end() && iter->second.CanBeMergedWith(next_vma->second)) {
+ iter->second.size += next_vma->second.size;
+ vma_map.erase(next_vma);
+ }
+
+ if (iter != vma_map.begin()) {
+ VMAIter prev_vma{std::prev(iter)};
+ if (prev_vma->second.CanBeMergedWith(iter->second)) {
+ prev_vma->second.size += iter->second.size;
+ vma_map.erase(iter);
+ iter = prev_vma;
}
}
- return results;
+
+ return iter;
}
-VAddr& MemoryManager::PageSlot(GPUVAddr gpu_addr) {
- auto& block{page_table[(gpu_addr >> (PAGE_BITS + PAGE_TABLE_BITS)) & PAGE_TABLE_MASK]};
- if (!block) {
- block = std::make_unique<PageBlock>();
- block->fill(static_cast<VAddr>(PageStatus::Unmapped));
+void MemoryManager::UpdatePageTableForVMA(const VirtualMemoryArea& vma) {
+ switch (vma.type) {
+ case VirtualMemoryArea::Type::Unmapped:
+ UnmapRegion(vma.base, vma.size);
+ break;
+ case VirtualMemoryArea::Type::Allocated:
+ MapMemoryRegion(vma.base, vma.size, nullptr, vma.backing_addr);
+ break;
+ case VirtualMemoryArea::Type::Mapped:
+ MapMemoryRegion(vma.base, vma.size, vma.backing_memory, vma.backing_addr);
+ break;
}
- return (*block)[(gpu_addr >> PAGE_BITS) & PAGE_BLOCK_MASK];
}
} // namespace Tegra
diff --git a/src/video_core/memory_manager.h b/src/video_core/memory_manager.h
index fb03497ca..43a84bd52 100644
--- a/src/video_core/memory_manager.h
+++ b/src/video_core/memory_manager.h
@@ -1,67 +1,178 @@
-// Copyright 2018 yuzu emulator team
+// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
-#include <array>
-#include <memory>
+#include <map>
#include <optional>
-#include <vector>
#include "common/common_types.h"
+#include "common/page_table.h"
+
+namespace VideoCore {
+class RasterizerInterface;
+}
namespace Tegra {
-/// Virtual addresses in the GPU's memory map are 64 bit.
-using GPUVAddr = u64;
+/**
+ * Represents a VMA in an address space. A VMA is a contiguous region of virtual addressing space
+ * with homogeneous attributes across its extents. In this particular implementation each VMA is
+ * also backed by a single host memory allocation.
+ */
+struct VirtualMemoryArea {
+ enum class Type : u8 {
+ Unmapped,
+ Allocated,
+ Mapped,
+ };
+
+ /// Virtual base address of the region.
+ GPUVAddr base{};
+ /// Size of the region.
+ u64 size{};
+ /// Memory area mapping type.
+ Type type{Type::Unmapped};
+ /// CPU memory mapped address corresponding to this memory area.
+ VAddr backing_addr{};
+ /// Offset into the backing_memory the mapping starts from.
+ std::size_t offset{};
+ /// Pointer backing this VMA.
+ u8* backing_memory{};
+
+ /// Tests if this area can be merged to the right with `next`.
+ bool CanBeMergedWith(const VirtualMemoryArea& next) const;
+};
class MemoryManager final {
public:
- MemoryManager();
+ explicit MemoryManager(VideoCore::RasterizerInterface& rasterizer);
+ ~MemoryManager();
GPUVAddr AllocateSpace(u64 size, u64 align);
- GPUVAddr AllocateSpace(GPUVAddr gpu_addr, u64 size, u64 align);
+ GPUVAddr AllocateSpace(GPUVAddr addr, u64 size, u64 align);
GPUVAddr MapBufferEx(VAddr cpu_addr, u64 size);
- GPUVAddr MapBufferEx(VAddr cpu_addr, GPUVAddr gpu_addr, u64 size);
- GPUVAddr UnmapBuffer(GPUVAddr gpu_addr, u64 size);
- GPUVAddr GetRegionEnd(GPUVAddr region_start) const;
- std::optional<VAddr> GpuToCpuAddress(GPUVAddr gpu_addr);
- std::vector<GPUVAddr> CpuToGpuAddress(VAddr cpu_addr) const;
+ GPUVAddr MapBufferEx(VAddr cpu_addr, GPUVAddr addr, u64 size);
+ GPUVAddr UnmapBuffer(GPUVAddr addr, u64 size);
+ std::optional<VAddr> GpuToCpuAddress(GPUVAddr addr) const;
+
+ template <typename T>
+ T Read(GPUVAddr addr) const;
+
+ template <typename T>
+ void Write(GPUVAddr addr, T data);
- static constexpr u64 PAGE_BITS = 16;
- static constexpr u64 PAGE_SIZE = 1 << PAGE_BITS;
- static constexpr u64 PAGE_MASK = PAGE_SIZE - 1;
+ u8* GetPointer(GPUVAddr addr);
+ const u8* GetPointer(GPUVAddr addr) const;
+
+ /// Returns true if the block is continuous in host memory, false otherwise
+ bool IsBlockContinuous(GPUVAddr start, std::size_t size) const;
+
+ /**
+ * 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
+ * in the Host Memory counterpart. Note: This functions cause Host GPU Memory
+ * Flushes and Invalidations, respectively to each operation.
+ */
+ void ReadBlock(GPUVAddr src_addr, void* dest_buffer, std::size_t size) const;
+ void WriteBlock(GPUVAddr dest_addr, const void* src_buffer, std::size_t size);
+ void CopyBlock(GPUVAddr dest_addr, GPUVAddr src_addr, std::size_t size);
+
+ /**
+ * ReadBlockUnsafe and WriteBlockUnsafe are special versions of ReadBlock and
+ * WriteBlock respectively. In this versions, no flushing or invalidation is actually
+ * done and their performance is similar to a memcpy. This functions can be used
+ * on either of this 2 scenarios instead of their safe counterpart:
+ * - Memory which is sure to never be represented in the Host GPU.
+ * - Memory Managed by a Cache Manager. Example: Texture Flushing should use
+ * WriteBlockUnsafe instead of WriteBlock since it shouldn't invalidate the texture
+ * being flushed.
+ */
+ void ReadBlockUnsafe(GPUVAddr src_addr, void* dest_buffer, std::size_t size) const;
+ void WriteBlockUnsafe(GPUVAddr dest_addr, const void* src_buffer, std::size_t size);
+ void CopyBlockUnsafe(GPUVAddr dest_addr, GPUVAddr src_addr, std::size_t size);
private:
- enum class PageStatus : u64 {
- Unmapped = 0xFFFFFFFFFFFFFFFFULL,
- Allocated = 0xFFFFFFFFFFFFFFFEULL,
- Reserved = 0xFFFFFFFFFFFFFFFDULL,
- };
+ using VMAMap = std::map<GPUVAddr, VirtualMemoryArea>;
+ using VMAHandle = VMAMap::const_iterator;
+ using VMAIter = VMAMap::iterator;
- std::optional<GPUVAddr> FindFreeBlock(GPUVAddr region_start, u64 size, u64 align,
- PageStatus status);
- VAddr& PageSlot(GPUVAddr gpu_addr);
-
- static constexpr u64 MAX_ADDRESS{0x10000000000ULL};
- static constexpr u64 PAGE_TABLE_BITS{10};
- static constexpr u64 PAGE_TABLE_SIZE{1 << PAGE_TABLE_BITS};
- static constexpr u64 PAGE_TABLE_MASK{PAGE_TABLE_SIZE - 1};
- static constexpr u64 PAGE_BLOCK_BITS{14};
- static constexpr u64 PAGE_BLOCK_SIZE{1 << PAGE_BLOCK_BITS};
- static constexpr u64 PAGE_BLOCK_MASK{PAGE_BLOCK_SIZE - 1};
-
- using PageBlock = std::array<VAddr, PAGE_BLOCK_SIZE>;
- std::array<std::unique_ptr<PageBlock>, PAGE_TABLE_SIZE> page_table{};
-
- struct MappedRegion {
- VAddr cpu_addr;
- GPUVAddr gpu_addr;
- u64 size;
- };
+ bool IsAddressValid(GPUVAddr addr) const;
+ void MapPages(GPUVAddr base, u64 size, u8* memory, Common::PageType type,
+ VAddr backing_addr = 0);
+ void MapMemoryRegion(GPUVAddr base, u64 size, u8* target, VAddr backing_addr);
+ void UnmapRegion(GPUVAddr base, u64 size);
+
+ /// Finds the VMA in which the given address is included in, or `vma_map.end()`.
+ VMAHandle FindVMA(GPUVAddr target) const;
+
+ VMAHandle AllocateMemory(GPUVAddr target, std::size_t offset, u64 size);
+
+ /**
+ * Maps an unmanaged host memory pointer at a given address.
+ *
+ * @param target The guest address to start the mapping at.
+ * @param memory The memory to be mapped.
+ * @param size Size of the mapping in bytes.
+ * @param backing_addr The base address of the range to back this mapping.
+ */
+ VMAHandle MapBackingMemory(GPUVAddr target, u8* memory, u64 size, VAddr backing_addr);
+
+ /// Unmaps a range of addresses, splitting VMAs as necessary.
+ void UnmapRange(GPUVAddr target, u64 size);
+
+ /// Converts a VMAHandle to a mutable VMAIter.
+ VMAIter StripIterConstness(const VMAHandle& iter);
+
+ /// Marks as the specified VMA as allocated.
+ VMAIter Allocate(VMAIter vma);
+
+ /**
+ * Carves a VMA of a specific size at the specified address by splitting Free VMAs while doing
+ * the appropriate error checking.
+ */
+ VMAIter CarveVMA(GPUVAddr base, u64 size);
+
+ /**
+ * Splits the edges of the given range of non-Free VMAs so that there is a VMA split at each
+ * end of the range.
+ */
+ VMAIter CarveVMARange(GPUVAddr base, u64 size);
+
+ /**
+ * Splits a VMA in two, at the specified offset.
+ * @returns the right side of the split, with the original iterator becoming the left side.
+ */
+ VMAIter SplitVMA(VMAIter vma, u64 offset_in_vma);
+
+ /**
+ * Checks for and merges the specified VMA with adjacent ones if possible.
+ * @returns the merged VMA or the original if no merging was possible.
+ */
+ VMAIter MergeAdjacent(VMAIter vma);
+
+ /// Updates the pages corresponding to this VMA so they match the VMA's attributes.
+ void UpdatePageTableForVMA(const VirtualMemoryArea& vma);
+
+ /// Finds a free (unmapped region) of the specified size starting at the specified address.
+ GPUVAddr FindFreeRegion(GPUVAddr region_start, u64 size) const;
+
+private:
+ static constexpr u64 page_bits{16};
+ static constexpr u64 page_size{1 << page_bits};
+ static constexpr u64 page_mask{page_size - 1};
+
+ /// Address space in bits, according to Tegra X1 TRM
+ static constexpr u32 address_space_width{40};
+ /// Start address for mapping, this is fairly arbitrary but must be non-zero.
+ static constexpr GPUVAddr address_space_base{0x100000};
+ /// End of address space, based on address space in bits.
+ static constexpr GPUVAddr address_space_end{1ULL << address_space_width};
- std::vector<MappedRegion> mapped_regions;
+ Common::PageTable page_table{page_bits};
+ VMAMap vma_map;
+ VideoCore::RasterizerInterface& rasterizer;
};
} // namespace Tegra
diff --git a/src/video_core/morton.cpp b/src/video_core/morton.cpp
index b68f4fb13..3e91cbc83 100644
--- a/src/video_core/morton.cpp
+++ b/src/video_core/morton.cpp
@@ -6,7 +6,6 @@
#include <cstring>
#include "common/assert.h"
#include "common/common_types.h"
-#include "core/memory.h"
#include "video_core/morton.h"
#include "video_core/surface.h"
#include "video_core/textures/decoders.h"
@@ -16,12 +15,12 @@ namespace VideoCore {
using Surface::GetBytesPerPixel;
using Surface::PixelFormat;
-using MortonCopyFn = void (*)(u32, u32, u32, u32, u32, u32, u8*, std::size_t, VAddr);
+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, std::size_t buffer_size, VAddr addr) {
+ 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
@@ -34,150 +33,146 @@ static void MortonCopy(u32 stride, u32 block_height, u32 height, u32 block_depth
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, Memory::GetPointer(addr), buffer, false,
- block_height, block_depth, tile_width_spacing);
+ 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 = {
- // clang-format off
- MortonCopy<true, PixelFormat::ABGR8U>,
- MortonCopy<true, PixelFormat::ABGR8S>,
- MortonCopy<true, PixelFormat::ABGR8UI>,
- MortonCopy<true, PixelFormat::B5G6R5U>,
- MortonCopy<true, PixelFormat::A2B10G10R10U>,
- MortonCopy<true, PixelFormat::A1B5G5R5U>,
- MortonCopy<true, PixelFormat::R8U>,
- MortonCopy<true, PixelFormat::R8UI>,
- MortonCopy<true, PixelFormat::RGBA16F>,
- MortonCopy<true, PixelFormat::RGBA16U>,
- MortonCopy<true, PixelFormat::RGBA16UI>,
- MortonCopy<true, PixelFormat::R11FG11FB10F>,
- MortonCopy<true, PixelFormat::RGBA32UI>,
- MortonCopy<true, PixelFormat::DXT1>,
- MortonCopy<true, PixelFormat::DXT23>,
- MortonCopy<true, PixelFormat::DXT45>,
- MortonCopy<true, PixelFormat::DXN1>,
- MortonCopy<true, PixelFormat::DXN2UNORM>,
- MortonCopy<true, PixelFormat::DXN2SNORM>,
- MortonCopy<true, PixelFormat::BC7U>,
- MortonCopy<true, PixelFormat::BC6H_UF16>,
- MortonCopy<true, PixelFormat::BC6H_SF16>,
- MortonCopy<true, PixelFormat::ASTC_2D_4X4>,
- MortonCopy<true, PixelFormat::BGRA8>,
- MortonCopy<true, PixelFormat::RGBA32F>,
- MortonCopy<true, PixelFormat::RG32F>,
- MortonCopy<true, PixelFormat::R32F>,
- MortonCopy<true, PixelFormat::R16F>,
- MortonCopy<true, PixelFormat::R16U>,
- MortonCopy<true, PixelFormat::R16S>,
- MortonCopy<true, PixelFormat::R16UI>,
- MortonCopy<true, PixelFormat::R16I>,
- MortonCopy<true, PixelFormat::RG16>,
- MortonCopy<true, PixelFormat::RG16F>,
- MortonCopy<true, PixelFormat::RG16UI>,
- MortonCopy<true, PixelFormat::RG16I>,
- MortonCopy<true, PixelFormat::RG16S>,
- MortonCopy<true, PixelFormat::RGB32F>,
- MortonCopy<true, PixelFormat::RGBA8_SRGB>,
- MortonCopy<true, PixelFormat::RG8U>,
- MortonCopy<true, PixelFormat::RG8S>,
- MortonCopy<true, PixelFormat::RG32UI>,
- MortonCopy<true, PixelFormat::R32UI>,
- MortonCopy<true, PixelFormat::ASTC_2D_8X8>,
- MortonCopy<true, PixelFormat::ASTC_2D_8X5>,
- MortonCopy<true, PixelFormat::ASTC_2D_5X4>,
- MortonCopy<true, PixelFormat::BGRA8_SRGB>,
- MortonCopy<true, PixelFormat::DXT1_SRGB>,
- MortonCopy<true, PixelFormat::DXT23_SRGB>,
- MortonCopy<true, PixelFormat::DXT45_SRGB>,
- MortonCopy<true, PixelFormat::BC7U_SRGB>,
- 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>,
- MortonCopy<true, PixelFormat::ASTC_2D_5X5_SRGB>,
- MortonCopy<true, PixelFormat::ASTC_2D_10X8>,
- MortonCopy<true, PixelFormat::ASTC_2D_10X8_SRGB>,
- MortonCopy<true, PixelFormat::Z32F>,
- MortonCopy<true, PixelFormat::Z16>,
- MortonCopy<true, PixelFormat::Z24S8>,
- MortonCopy<true, PixelFormat::S8Z24>,
- MortonCopy<true, PixelFormat::Z32FS8>,
- // clang-format on
+ MortonCopy<true, PixelFormat::ABGR8U>,
+ MortonCopy<true, PixelFormat::ABGR8S>,
+ MortonCopy<true, PixelFormat::ABGR8UI>,
+ MortonCopy<true, PixelFormat::B5G6R5U>,
+ MortonCopy<true, PixelFormat::A2B10G10R10U>,
+ MortonCopy<true, PixelFormat::A1B5G5R5U>,
+ MortonCopy<true, PixelFormat::R8U>,
+ MortonCopy<true, PixelFormat::R8UI>,
+ MortonCopy<true, PixelFormat::RGBA16F>,
+ MortonCopy<true, PixelFormat::RGBA16U>,
+ MortonCopy<true, PixelFormat::RGBA16UI>,
+ MortonCopy<true, PixelFormat::R11FG11FB10F>,
+ MortonCopy<true, PixelFormat::RGBA32UI>,
+ MortonCopy<true, PixelFormat::DXT1>,
+ MortonCopy<true, PixelFormat::DXT23>,
+ MortonCopy<true, PixelFormat::DXT45>,
+ MortonCopy<true, PixelFormat::DXN1>,
+ MortonCopy<true, PixelFormat::DXN2UNORM>,
+ MortonCopy<true, PixelFormat::DXN2SNORM>,
+ MortonCopy<true, PixelFormat::BC7U>,
+ MortonCopy<true, PixelFormat::BC6H_UF16>,
+ MortonCopy<true, PixelFormat::BC6H_SF16>,
+ MortonCopy<true, PixelFormat::ASTC_2D_4X4>,
+ MortonCopy<true, PixelFormat::BGRA8>,
+ MortonCopy<true, PixelFormat::RGBA32F>,
+ MortonCopy<true, PixelFormat::RG32F>,
+ MortonCopy<true, PixelFormat::R32F>,
+ MortonCopy<true, PixelFormat::R16F>,
+ MortonCopy<true, PixelFormat::R16U>,
+ MortonCopy<true, PixelFormat::R16S>,
+ MortonCopy<true, PixelFormat::R16UI>,
+ MortonCopy<true, PixelFormat::R16I>,
+ MortonCopy<true, PixelFormat::RG16>,
+ MortonCopy<true, PixelFormat::RG16F>,
+ MortonCopy<true, PixelFormat::RG16UI>,
+ MortonCopy<true, PixelFormat::RG16I>,
+ MortonCopy<true, PixelFormat::RG16S>,
+ MortonCopy<true, PixelFormat::RGB32F>,
+ MortonCopy<true, PixelFormat::RGBA8_SRGB>,
+ MortonCopy<true, PixelFormat::RG8U>,
+ MortonCopy<true, PixelFormat::RG8S>,
+ MortonCopy<true, PixelFormat::RG32UI>,
+ MortonCopy<true, PixelFormat::R32UI>,
+ MortonCopy<true, PixelFormat::ASTC_2D_8X8>,
+ MortonCopy<true, PixelFormat::ASTC_2D_8X5>,
+ MortonCopy<true, PixelFormat::ASTC_2D_5X4>,
+ MortonCopy<true, PixelFormat::BGRA8_SRGB>,
+ MortonCopy<true, PixelFormat::DXT1_SRGB>,
+ MortonCopy<true, PixelFormat::DXT23_SRGB>,
+ MortonCopy<true, PixelFormat::DXT45_SRGB>,
+ MortonCopy<true, PixelFormat::BC7U_SRGB>,
+ 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>,
+ MortonCopy<true, PixelFormat::ASTC_2D_5X5_SRGB>,
+ MortonCopy<true, PixelFormat::ASTC_2D_10X8>,
+ MortonCopy<true, PixelFormat::ASTC_2D_10X8_SRGB>,
+ MortonCopy<true, PixelFormat::Z32F>,
+ MortonCopy<true, PixelFormat::Z16>,
+ MortonCopy<true, PixelFormat::Z24S8>,
+ MortonCopy<true, PixelFormat::S8Z24>,
+ MortonCopy<true, PixelFormat::Z32FS8>,
};
static constexpr ConversionArray linear_to_morton_fns = {
- // clang-format off
- MortonCopy<false, PixelFormat::ABGR8U>,
- MortonCopy<false, PixelFormat::ABGR8S>,
- MortonCopy<false, PixelFormat::ABGR8UI>,
- MortonCopy<false, PixelFormat::B5G6R5U>,
- MortonCopy<false, PixelFormat::A2B10G10R10U>,
- MortonCopy<false, PixelFormat::A1B5G5R5U>,
- MortonCopy<false, PixelFormat::R8U>,
- MortonCopy<false, PixelFormat::R8UI>,
- MortonCopy<false, PixelFormat::RGBA16F>,
- MortonCopy<false, PixelFormat::RGBA16U>,
- MortonCopy<false, PixelFormat::RGBA16UI>,
- MortonCopy<false, PixelFormat::R11FG11FB10F>,
- MortonCopy<false, PixelFormat::RGBA32UI>,
- MortonCopy<false, PixelFormat::DXT1>,
- MortonCopy<false, PixelFormat::DXT23>,
- MortonCopy<false, PixelFormat::DXT45>,
- MortonCopy<false, PixelFormat::DXN1>,
- MortonCopy<false, PixelFormat::DXN2UNORM>,
- MortonCopy<false, PixelFormat::DXN2SNORM>,
- MortonCopy<false, PixelFormat::BC7U>,
- MortonCopy<false, PixelFormat::BC6H_UF16>,
- MortonCopy<false, PixelFormat::BC6H_SF16>,
- // TODO(Subv): Swizzling ASTC formats are not supported
- nullptr,
- MortonCopy<false, PixelFormat::BGRA8>,
- MortonCopy<false, PixelFormat::RGBA32F>,
- MortonCopy<false, PixelFormat::RG32F>,
- MortonCopy<false, PixelFormat::R32F>,
- MortonCopy<false, PixelFormat::R16F>,
- MortonCopy<false, PixelFormat::R16U>,
- MortonCopy<false, PixelFormat::R16S>,
- MortonCopy<false, PixelFormat::R16UI>,
- MortonCopy<false, PixelFormat::R16I>,
- MortonCopy<false, PixelFormat::RG16>,
- MortonCopy<false, PixelFormat::RG16F>,
- MortonCopy<false, PixelFormat::RG16UI>,
- MortonCopy<false, PixelFormat::RG16I>,
- MortonCopy<false, PixelFormat::RG16S>,
- MortonCopy<false, PixelFormat::RGB32F>,
- MortonCopy<false, PixelFormat::RGBA8_SRGB>,
- MortonCopy<false, PixelFormat::RG8U>,
- MortonCopy<false, PixelFormat::RG8S>,
- MortonCopy<false, PixelFormat::RG32UI>,
- MortonCopy<false, PixelFormat::R32UI>,
- nullptr,
- nullptr,
- nullptr,
- MortonCopy<false, PixelFormat::BGRA8_SRGB>,
- MortonCopy<false, PixelFormat::DXT1_SRGB>,
- MortonCopy<false, PixelFormat::DXT23_SRGB>,
- MortonCopy<false, PixelFormat::DXT45_SRGB>,
- MortonCopy<false, PixelFormat::BC7U_SRGB>,
- nullptr,
- nullptr,
- nullptr,
- nullptr,
- nullptr,
- nullptr,
- nullptr,
- nullptr,
- MortonCopy<false, PixelFormat::Z32F>,
- MortonCopy<false, PixelFormat::Z16>,
- MortonCopy<false, PixelFormat::Z24S8>,
- MortonCopy<false, PixelFormat::S8Z24>,
- MortonCopy<false, PixelFormat::Z32FS8>,
- // clang-format on
+ MortonCopy<false, PixelFormat::ABGR8U>,
+ MortonCopy<false, PixelFormat::ABGR8S>,
+ MortonCopy<false, PixelFormat::ABGR8UI>,
+ MortonCopy<false, PixelFormat::B5G6R5U>,
+ MortonCopy<false, PixelFormat::A2B10G10R10U>,
+ MortonCopy<false, PixelFormat::A1B5G5R5U>,
+ MortonCopy<false, PixelFormat::R8U>,
+ MortonCopy<false, PixelFormat::R8UI>,
+ MortonCopy<false, PixelFormat::RGBA16F>,
+ MortonCopy<false, PixelFormat::RGBA16U>,
+ MortonCopy<false, PixelFormat::RGBA16UI>,
+ MortonCopy<false, PixelFormat::R11FG11FB10F>,
+ MortonCopy<false, PixelFormat::RGBA32UI>,
+ MortonCopy<false, PixelFormat::DXT1>,
+ MortonCopy<false, PixelFormat::DXT23>,
+ MortonCopy<false, PixelFormat::DXT45>,
+ MortonCopy<false, PixelFormat::DXN1>,
+ MortonCopy<false, PixelFormat::DXN2UNORM>,
+ MortonCopy<false, PixelFormat::DXN2SNORM>,
+ MortonCopy<false, PixelFormat::BC7U>,
+ MortonCopy<false, PixelFormat::BC6H_UF16>,
+ MortonCopy<false, PixelFormat::BC6H_SF16>,
+ // TODO(Subv): Swizzling ASTC formats are not supported
+ nullptr,
+ MortonCopy<false, PixelFormat::BGRA8>,
+ MortonCopy<false, PixelFormat::RGBA32F>,
+ MortonCopy<false, PixelFormat::RG32F>,
+ MortonCopy<false, PixelFormat::R32F>,
+ MortonCopy<false, PixelFormat::R16F>,
+ MortonCopy<false, PixelFormat::R16U>,
+ MortonCopy<false, PixelFormat::R16S>,
+ MortonCopy<false, PixelFormat::R16UI>,
+ MortonCopy<false, PixelFormat::R16I>,
+ MortonCopy<false, PixelFormat::RG16>,
+ MortonCopy<false, PixelFormat::RG16F>,
+ MortonCopy<false, PixelFormat::RG16UI>,
+ MortonCopy<false, PixelFormat::RG16I>,
+ MortonCopy<false, PixelFormat::RG16S>,
+ MortonCopy<false, PixelFormat::RGB32F>,
+ MortonCopy<false, PixelFormat::RGBA8_SRGB>,
+ MortonCopy<false, PixelFormat::RG8U>,
+ MortonCopy<false, PixelFormat::RG8S>,
+ MortonCopy<false, PixelFormat::RG32UI>,
+ MortonCopy<false, PixelFormat::R32UI>,
+ nullptr,
+ nullptr,
+ nullptr,
+ MortonCopy<false, PixelFormat::BGRA8_SRGB>,
+ MortonCopy<false, PixelFormat::DXT1_SRGB>,
+ MortonCopy<false, PixelFormat::DXT23_SRGB>,
+ MortonCopy<false, PixelFormat::DXT45_SRGB>,
+ MortonCopy<false, PixelFormat::BC7U_SRGB>,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ MortonCopy<false, PixelFormat::Z32F>,
+ MortonCopy<false, PixelFormat::Z16>,
+ MortonCopy<false, PixelFormat::Z24S8>,
+ MortonCopy<false, PixelFormat::S8Z24>,
+ MortonCopy<false, PixelFormat::Z32FS8>,
};
static MortonCopyFn GetSwizzleFunction(MortonSwizzleMode mode, Surface::PixelFormat format) {
@@ -191,45 +186,6 @@ static MortonCopyFn GetSwizzleFunction(MortonSwizzleMode mode, Surface::PixelFor
return morton_to_linear_fns[static_cast<std::size_t>(format)];
}
-/// 8x8 Z-Order coordinate from 2D coordinates
-static u32 MortonInterleave(u32 x, u32 y) {
- static const u32 xlut[] = {0x00, 0x01, 0x04, 0x05, 0x10, 0x11, 0x14, 0x15};
- static const u32 ylut[] = {0x00, 0x02, 0x08, 0x0a, 0x20, 0x22, 0x28, 0x2a};
- return xlut[x % 8] + ylut[y % 8];
-}
-
-/// Calculates the offset of the position of the pixel in Morton order
-static u32 GetMortonOffset(u32 x, u32 y, u32 bytes_per_pixel) {
- // Images are split into 8x8 tiles. Each tile is composed of four 4x4 subtiles each
- // of which is composed of four 2x2 subtiles each of which is composed of four texels.
- // Each structure is embedded into the next-bigger one in a diagonal pattern, e.g.
- // texels are laid out in a 2x2 subtile like this:
- // 2 3
- // 0 1
- //
- // The full 8x8 tile has the texels arranged like this:
- //
- // 42 43 46 47 58 59 62 63
- // 40 41 44 45 56 57 60 61
- // 34 35 38 39 50 51 54 55
- // 32 33 36 37 48 49 52 53
- // 10 11 14 15 26 27 30 31
- // 08 09 12 13 24 25 28 29
- // 02 03 06 07 18 19 22 23
- // 00 01 04 05 16 17 20 21
- //
- // This pattern is what's called Z-order curve, or Morton order.
-
- const unsigned int block_height = 8;
- const unsigned int coarse_x = x & ~7;
-
- u32 i = MortonInterleave(x, y);
-
- const unsigned int offset = coarse_x * block_height;
-
- return (i + offset) * bytes_per_pixel;
-}
-
static u32 MortonInterleave128(u32 x, u32 y) {
// 128x128 Z-Order coordinate from 2D coordinates
static constexpr u32 xlut[] = {
@@ -325,14 +281,14 @@ static u32 GetMortonOffset128(u32 x, u32 y, u32 bytes_per_pixel) {
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, std::size_t buffer_size, VAddr addr) {
-
+ u8* buffer, u8* addr) {
GetSwizzleFunction(mode, format)(stride, block_height, height, block_depth, depth,
- tile_width_spacing, buffer, buffer_size, addr);
+ tile_width_spacing, buffer, addr);
}
-void MortonCopyPixels128(u32 width, u32 height, u32 bytes_per_pixel, u32 linear_bytes_per_pixel,
- u8* morton_data, u8* linear_data, bool morton_to_linear) {
+void MortonCopyPixels128(MortonSwizzleMode mode, u32 width, u32 height, u32 bytes_per_pixel,
+ u32 linear_bytes_per_pixel, u8* morton_data, u8* linear_data) {
+ const bool morton_to_linear = mode == MortonSwizzleMode::MortonToLinear;
u8* data_ptrs[2];
for (u32 y = 0; y < height; ++y) {
for (u32 x = 0; x < width; ++x) {
diff --git a/src/video_core/morton.h b/src/video_core/morton.h
index 065f59ce3..ee5b45555 100644
--- a/src/video_core/morton.h
+++ b/src/video_core/morton.h
@@ -13,9 +13,9 @@ 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, std::size_t buffer_size, VAddr addr);
+ u8* buffer, u8* addr);
-void MortonCopyPixels128(u32 width, u32 height, u32 bytes_per_pixel, u32 linear_bytes_per_pixel,
- u8* morton_data, u8* linear_data, bool morton_to_linear);
+void MortonCopyPixels128(MortonSwizzleMode mode, u32 width, u32 height, u32 bytes_per_pixel,
+ u32 linear_bytes_per_pixel, u8* morton_data, u8* linear_data);
} // namespace VideoCore
diff --git a/src/video_core/rasterizer_cache.h b/src/video_core/rasterizer_cache.h
index a7bcf26fb..0c4ea1494 100644
--- a/src/video_core/rasterizer_cache.h
+++ b/src/video_core/rasterizer_cache.h
@@ -4,6 +4,7 @@
#pragma once
+#include <mutex>
#include <set>
#include <unordered_map>
@@ -12,21 +13,30 @@
#include "common/common_types.h"
#include "core/settings.h"
+#include "video_core/gpu.h"
#include "video_core/rasterizer_interface.h"
class RasterizerCacheObject {
public:
+ explicit RasterizerCacheObject(const u8* host_ptr)
+ : host_ptr{host_ptr}, cache_addr{ToCacheAddr(host_ptr)} {}
+
virtual ~RasterizerCacheObject();
+ CacheAddr GetCacheAddr() const {
+ return cache_addr;
+ }
+
+ const u8* GetHostPtr() const {
+ return host_ptr;
+ }
+
/// Gets the address of the shader in guest memory, required for cache management
- virtual VAddr GetAddr() const = 0;
+ virtual VAddr GetCpuAddr() const = 0;
/// Gets the size of the shader in guest memory, required for cache management
virtual std::size_t GetSizeInBytes() const = 0;
- /// Wriets any cached resources back to memory
- virtual void Flush() = 0;
-
/// Sets whether the cached object should be considered registered
void SetIsRegistered(bool registered) {
is_registered = registered;
@@ -58,6 +68,8 @@ private:
bool is_registered{}; ///< Whether the object is currently registered with the cache
bool is_dirty{}; ///< Whether the object is dirty (out of sync with guest memory)
u64 last_modified_ticks{}; ///< When the object was last modified, used for in-order flushing
+ const u8* host_ptr{}; ///< Pointer to the memory backing this cached region
+ CacheAddr cache_addr{}; ///< Cache address memory, unique from emulated virtual address space
};
template <class T>
@@ -68,7 +80,9 @@ public:
explicit RasterizerCache(VideoCore::RasterizerInterface& rasterizer) : rasterizer{rasterizer} {}
/// Write any cached resources overlapping the specified region back to memory
- void FlushRegion(Tegra::GPUVAddr addr, size_t size) {
+ void FlushRegion(CacheAddr addr, std::size_t size) {
+ std::lock_guard lock{mutex};
+
const auto& objects{GetSortedObjectsFromRegion(addr, size)};
for (auto& object : objects) {
FlushObject(object);
@@ -76,7 +90,9 @@ public:
}
/// Mark the specified region as being invalidated
- void InvalidateRegion(VAddr addr, u64 size) {
+ void InvalidateRegion(CacheAddr addr, u64 size) {
+ std::lock_guard lock{mutex};
+
const auto& objects{GetSortedObjectsFromRegion(addr, size)};
for (auto& object : objects) {
if (!object->IsRegistered()) {
@@ -89,58 +105,73 @@ public:
/// Invalidates everything in the cache
void InvalidateAll() {
+ std::lock_guard lock{mutex};
+
while (interval_cache.begin() != interval_cache.end()) {
Unregister(*interval_cache.begin()->second.begin());
}
}
protected:
- /// Tries to get an object from the cache with the specified address
- T TryGet(VAddr addr) const {
+ /// Tries to get an object from the cache with the specified cache address
+ T TryGet(CacheAddr addr) const {
const auto iter = map_cache.find(addr);
if (iter != map_cache.end())
return iter->second;
return nullptr;
}
+ T TryGet(const void* addr) const {
+ const auto iter = map_cache.find(ToCacheAddr(addr));
+ if (iter != map_cache.end())
+ return iter->second;
+ return nullptr;
+ }
+
/// Register an object into the cache
- void Register(const T& object) {
+ virtual void Register(const T& object) {
+ std::lock_guard lock{mutex};
+
object->SetIsRegistered(true);
interval_cache.add({GetInterval(object), ObjectSet{object}});
- map_cache.insert({object->GetAddr(), object});
- rasterizer.UpdatePagesCachedCount(object->GetAddr(), object->GetSizeInBytes(), 1);
+ map_cache.insert({object->GetCacheAddr(), object});
+ rasterizer.UpdatePagesCachedCount(object->GetCpuAddr(), object->GetSizeInBytes(), 1);
}
/// Unregisters an object from the cache
- void Unregister(const T& object) {
- object->SetIsRegistered(false);
- rasterizer.UpdatePagesCachedCount(object->GetAddr(), object->GetSizeInBytes(), -1);
- // Only flush if use_accurate_gpu_emulation is enabled, as it incurs a performance hit
- if (Settings::values.use_accurate_gpu_emulation) {
- FlushObject(object);
- }
+ virtual void Unregister(const T& object) {
+ std::lock_guard lock{mutex};
+ object->SetIsRegistered(false);
+ rasterizer.UpdatePagesCachedCount(object->GetCpuAddr(), object->GetSizeInBytes(), -1);
+ const CacheAddr addr = object->GetCacheAddr();
interval_cache.subtract({GetInterval(object), ObjectSet{object}});
- map_cache.erase(object->GetAddr());
+ map_cache.erase(addr);
}
/// Returns a ticks counter used for tracking when cached objects were last modified
u64 GetModifiedTicks() {
+ std::lock_guard lock{mutex};
+
return ++modified_ticks;
}
+ virtual void FlushObjectInner(const T& object) = 0;
+
/// Flushes the specified object, updating appropriate cache state as needed
void FlushObject(const T& object) {
+ std::lock_guard lock{mutex};
+
if (!object->IsDirty()) {
return;
}
- object->Flush();
+ FlushObjectInner(object);
object->MarkAsModified(false, *this);
}
private:
/// Returns a list of cached objects from the specified memory region, ordered by access time
- std::vector<T> GetSortedObjectsFromRegion(VAddr addr, u64 size) {
+ std::vector<T> GetSortedObjectsFromRegion(CacheAddr addr, u64 size) {
if (size == 0) {
return {};
}
@@ -164,17 +195,18 @@ private:
}
using ObjectSet = std::set<T>;
- using ObjectCache = std::unordered_map<VAddr, T>;
- using IntervalCache = boost::icl::interval_map<VAddr, ObjectSet>;
+ using ObjectCache = std::unordered_map<CacheAddr, T>;
+ using IntervalCache = boost::icl::interval_map<CacheAddr, ObjectSet>;
using ObjectInterval = typename IntervalCache::interval_type;
static auto GetInterval(const T& object) {
- return ObjectInterval::right_open(object->GetAddr(),
- object->GetAddr() + object->GetSizeInBytes());
+ return ObjectInterval::right_open(object->GetCacheAddr(),
+ object->GetCacheAddr() + object->GetSizeInBytes());
}
ObjectCache map_cache;
IntervalCache interval_cache; ///< Cache of objects
u64 modified_ticks{}; ///< Counter of cache state ticks, used for in-order flushing
VideoCore::RasterizerInterface& rasterizer;
+ std::recursive_mutex mutex;
};
diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h
index 6a1dc9cf6..d7b86df38 100644
--- a/src/video_core/rasterizer_interface.h
+++ b/src/video_core/rasterizer_interface.h
@@ -9,7 +9,6 @@
#include "common/common_types.h"
#include "video_core/engines/fermi_2d.h"
#include "video_core/gpu.h"
-#include "video_core/memory_manager.h"
namespace VideoCore {
@@ -35,14 +34,14 @@ public:
virtual void FlushAll() = 0;
/// Notify rasterizer that any caches of the specified region should be flushed to Switch memory
- virtual void FlushRegion(VAddr addr, u64 size) = 0;
+ virtual void FlushRegion(CacheAddr addr, u64 size) = 0;
/// Notify rasterizer that any caches of the specified region should be invalidated
- virtual void InvalidateRegion(VAddr addr, u64 size) = 0;
+ virtual void InvalidateRegion(CacheAddr 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;
+ virtual void FlushAndInvalidateRegion(CacheAddr addr, u64 size) = 0;
/// Attempt to use a faster method to perform a surface copy
virtual bool AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Regs::Surface& src,
@@ -63,7 +62,7 @@ public:
}
/// Increase/decrease the number of object in pages touching the specified region
- virtual void UpdatePagesCachedCount(Tegra::GPUVAddr addr, u64 size, int delta) {}
+ 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,
diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.cpp b/src/video_core/renderer_opengl/gl_buffer_cache.cpp
index b3062e5ba..48b86f3bd 100644
--- a/src/video_core/renderer_opengl/gl_buffer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_buffer_cache.cpp
@@ -7,30 +7,34 @@
#include "common/alignment.h"
#include "core/core.h"
-#include "core/memory.h"
+#include "video_core/memory_manager.h"
#include "video_core/renderer_opengl/gl_buffer_cache.h"
#include "video_core/renderer_opengl/gl_rasterizer.h"
namespace OpenGL {
+CachedBufferEntry::CachedBufferEntry(VAddr cpu_addr, std::size_t size, GLintptr offset,
+ std::size_t alignment, u8* host_ptr)
+ : RasterizerCacheObject{host_ptr}, cpu_addr{cpu_addr}, size{size}, offset{offset},
+ alignment{alignment} {}
+
OGLBufferCache::OGLBufferCache(RasterizerOpenGL& rasterizer, std::size_t size)
: RasterizerCache{rasterizer}, stream_buffer(size, true) {}
-GLintptr OGLBufferCache::UploadMemory(Tegra::GPUVAddr gpu_addr, std::size_t size,
- std::size_t alignment, bool cache) {
+GLintptr OGLBufferCache::UploadMemory(GPUVAddr gpu_addr, std::size_t size, std::size_t alignment,
+ bool cache) {
auto& memory_manager = Core::System::GetInstance().GPU().MemoryManager();
- const auto cpu_addr{memory_manager.GpuToCpuAddress(gpu_addr)};
- ASSERT_MSG(cpu_addr, "Invalid GPU address");
// 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.
cache &= size >= 2048;
+ const auto& host_ptr{memory_manager.GetPointer(gpu_addr)};
if (cache) {
- auto entry = TryGet(*cpu_addr);
+ auto entry = TryGet(host_ptr);
if (entry) {
- if (entry->size >= size && entry->alignment == alignment) {
- return entry->offset;
+ if (entry->GetSize() >= size && entry->GetAlignment() == alignment) {
+ return entry->GetOffset();
}
Unregister(entry);
}
@@ -39,17 +43,17 @@ GLintptr OGLBufferCache::UploadMemory(Tegra::GPUVAddr gpu_addr, std::size_t size
AlignBuffer(alignment);
const GLintptr uploaded_offset = buffer_offset;
- Memory::ReadBlock(*cpu_addr, buffer_ptr, size);
+ if (!host_ptr) {
+ return uploaded_offset;
+ }
+ std::memcpy(buffer_ptr, host_ptr, size);
buffer_ptr += size;
buffer_offset += size;
if (cache) {
- auto entry = std::make_shared<CachedBufferEntry>();
- entry->offset = uploaded_offset;
- entry->size = size;
- entry->alignment = alignment;
- entry->addr = *cpu_addr;
+ auto entry = std::make_shared<CachedBufferEntry>(
+ *memory_manager.GpuToCpuAddress(gpu_addr), size, uploaded_offset, alignment, host_ptr);
Register(entry);
}
@@ -67,16 +71,6 @@ GLintptr OGLBufferCache::UploadHostMemory(const void* raw_pointer, std::size_t s
return uploaded_offset;
}
-std::tuple<u8*, GLintptr> OGLBufferCache::ReserveMemory(std::size_t size, std::size_t alignment) {
- AlignBuffer(alignment);
- u8* const uploaded_ptr = buffer_ptr;
- const GLintptr uploaded_offset = buffer_offset;
-
- buffer_ptr += size;
- buffer_offset += size;
- return std::make_tuple(uploaded_ptr, uploaded_offset);
-}
-
bool OGLBufferCache::Map(std::size_t max_size) {
bool invalidate;
std::tie(buffer_ptr, buffer_offset_base, invalidate) =
diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.h b/src/video_core/renderer_opengl/gl_buffer_cache.h
index c11acfb79..f2347581b 100644
--- a/src/video_core/renderer_opengl/gl_buffer_cache.h
+++ b/src/video_core/renderer_opengl/gl_buffer_cache.h
@@ -17,22 +17,36 @@ namespace OpenGL {
class RasterizerOpenGL;
-struct CachedBufferEntry final : public RasterizerCacheObject {
- VAddr GetAddr() const override {
- return addr;
+class CachedBufferEntry final : public RasterizerCacheObject {
+public:
+ explicit CachedBufferEntry(VAddr cpu_addr, std::size_t size, GLintptr offset,
+ std::size_t alignment, u8* host_ptr);
+
+ VAddr GetCpuAddr() const override {
+ return cpu_addr;
}
std::size_t GetSizeInBytes() const override {
return size;
}
- // We do not have to flush this cache as things in it are never modified by us.
- void Flush() override {}
+ std::size_t GetSize() const {
+ return size;
+ }
+
+ GLintptr GetOffset() const {
+ return offset;
+ }
- VAddr addr;
- std::size_t size;
- GLintptr offset;
- std::size_t alignment;
+ std::size_t GetAlignment() const {
+ return alignment;
+ }
+
+private:
+ VAddr cpu_addr{};
+ std::size_t size{};
+ GLintptr offset{};
+ std::size_t alignment{};
};
class OGLBufferCache final : public RasterizerCache<std::shared_ptr<CachedBufferEntry>> {
@@ -41,15 +55,12 @@ public:
/// Uploads data from a guest GPU address. Returns host's buffer offset where it's been
/// allocated.
- GLintptr UploadMemory(Tegra::GPUVAddr gpu_addr, std::size_t size, std::size_t alignment = 4,
+ GLintptr UploadMemory(GPUVAddr gpu_addr, std::size_t size, std::size_t alignment = 4,
bool cache = true);
/// Uploads from a host memory. Returns host's buffer offset where it's been allocated.
GLintptr UploadHostMemory(const void* raw_pointer, std::size_t size, std::size_t alignment = 4);
- /// Reserves memory to be used by host's CPU. Returns mapped address and offset.
- std::tuple<u8*, GLintptr> ReserveMemory(std::size_t size, std::size_t alignment = 4);
-
bool Map(std::size_t max_size);
void Unmap();
@@ -58,6 +69,9 @@ public:
protected:
void AlignBuffer(std::size_t alignment);
+ // We do not have to flush this cache as things in it are never modified by us.
+ void FlushObjectInner(const std::shared_ptr<CachedBufferEntry>& object) override {}
+
private:
OGLStreamBuffer stream_buffer;
diff --git a/src/video_core/renderer_opengl/gl_device.cpp b/src/video_core/renderer_opengl/gl_device.cpp
new file mode 100644
index 000000000..65a88b06c
--- /dev/null
+++ b/src/video_core/renderer_opengl/gl_device.cpp
@@ -0,0 +1,109 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <array>
+#include <cstddef>
+#include <glad/glad.h>
+
+#include "common/logging/log.h"
+#include "common/scope_exit.h"
+#include "video_core/renderer_opengl/gl_device.h"
+#include "video_core/renderer_opengl/gl_resource_manager.h"
+
+namespace OpenGL {
+
+namespace {
+template <typename T>
+T GetInteger(GLenum pname) {
+ GLint temporary;
+ glGetIntegerv(pname, &temporary);
+ return static_cast<T>(temporary);
+}
+} // Anonymous namespace
+
+Device::Device() {
+ uniform_buffer_alignment = GetInteger<std::size_t>(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT);
+ max_vertex_attributes = GetInteger<u32>(GL_MAX_VERTEX_ATTRIBS);
+ max_varyings = GetInteger<u32>(GL_MAX_VARYING_VECTORS);
+ has_variable_aoffi = TestVariableAoffi();
+ has_component_indexing_bug = TestComponentIndexingBug();
+}
+
+Device::Device(std::nullptr_t) {
+ uniform_buffer_alignment = 0;
+ max_vertex_attributes = 16;
+ max_varyings = 15;
+ has_variable_aoffi = true;
+ has_component_indexing_bug = false;
+}
+
+bool Device::TestVariableAoffi() {
+ const GLchar* AOFFI_TEST = R"(#version 430 core
+// This is a unit test, please ignore me on apitrace bug reports.
+uniform sampler2D tex;
+uniform ivec2 variable_offset;
+void main() {
+ gl_Position = textureOffset(tex, vec2(0), variable_offset);
+}
+)";
+ const GLuint shader{glCreateShaderProgramv(GL_VERTEX_SHADER, 1, &AOFFI_TEST)};
+ GLint link_status{};
+ glGetProgramiv(shader, GL_LINK_STATUS, &link_status);
+ glDeleteProgram(shader);
+
+ const bool supported{link_status == GL_TRUE};
+ LOG_INFO(Render_OpenGL, "Renderer_VariableAOFFI: {}", supported);
+ return supported;
+}
+
+bool Device::TestComponentIndexingBug() {
+ constexpr char log_message[] = "Renderer_ComponentIndexingBug: {}";
+ const GLchar* COMPONENT_TEST = R"(#version 430 core
+layout (std430, binding = 0) buffer OutputBuffer {
+ uint output_value;
+};
+layout (std140, binding = 0) uniform InputBuffer {
+ uvec4 input_value[4096];
+};
+layout (location = 0) uniform uint idx;
+void main() {
+ output_value = input_value[idx >> 2][idx & 3];
+})";
+ const GLuint shader{glCreateShaderProgramv(GL_VERTEX_SHADER, 1, &COMPONENT_TEST)};
+ SCOPE_EXIT({ glDeleteProgram(shader); });
+ glUseProgram(shader);
+
+ OGLVertexArray vao;
+ vao.Create();
+ glBindVertexArray(vao.handle);
+
+ constexpr std::array<GLuint, 8> values{0, 0, 0, 0, 0x1236327, 0x985482, 0x872753, 0x2378432};
+ OGLBuffer ubo;
+ ubo.Create();
+ glNamedBufferData(ubo.handle, sizeof(values), values.data(), GL_STATIC_DRAW);
+ glBindBufferBase(GL_UNIFORM_BUFFER, 0, ubo.handle);
+
+ OGLBuffer ssbo;
+ ssbo.Create();
+ glNamedBufferStorage(ssbo.handle, sizeof(GLuint), nullptr, GL_CLIENT_STORAGE_BIT);
+
+ for (GLuint index = 4; index < 8; ++index) {
+ glInvalidateBufferData(ssbo.handle);
+ glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, ssbo.handle);
+
+ glProgramUniform1ui(shader, 0, index);
+ glDrawArrays(GL_POINTS, 0, 1);
+
+ GLuint result;
+ glGetNamedBufferSubData(ssbo.handle, 0, sizeof(result), &result);
+ if (result != values.at(index)) {
+ LOG_INFO(Render_OpenGL, log_message, true);
+ return true;
+ }
+ }
+ LOG_INFO(Render_OpenGL, log_message, false);
+ return false;
+}
+
+} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_device.h b/src/video_core/renderer_opengl/gl_device.h
new file mode 100644
index 000000000..8c8c93760
--- /dev/null
+++ b/src/video_core/renderer_opengl/gl_device.h
@@ -0,0 +1,48 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <cstddef>
+#include "common/common_types.h"
+
+namespace OpenGL {
+
+class Device {
+public:
+ explicit Device();
+ explicit Device(std::nullptr_t);
+
+ std::size_t GetUniformBufferAlignment() const {
+ return uniform_buffer_alignment;
+ }
+
+ u32 GetMaxVertexAttributes() const {
+ return max_vertex_attributes;
+ }
+
+ u32 GetMaxVaryings() const {
+ return max_varyings;
+ }
+
+ bool HasVariableAoffi() const {
+ return has_variable_aoffi;
+ }
+
+ bool HasComponentIndexingBug() const {
+ return has_component_indexing_bug;
+ }
+
+private:
+ static bool TestVariableAoffi();
+ static bool TestComponentIndexingBug();
+
+ std::size_t uniform_buffer_alignment{};
+ u32 max_vertex_attributes{};
+ u32 max_varyings{};
+ bool has_variable_aoffi{};
+ bool has_component_indexing_bug{};
+};
+
+} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_global_cache.cpp b/src/video_core/renderer_opengl/gl_global_cache.cpp
index c7f32feaa..ea4a593af 100644
--- a/src/video_core/renderer_opengl/gl_global_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_global_cache.cpp
@@ -4,10 +4,9 @@
#include <glad/glad.h>
-#include "common/assert.h"
#include "common/logging/log.h"
#include "core/core.h"
-#include "core/memory.h"
+#include "video_core/memory_manager.h"
#include "video_core/renderer_opengl/gl_global_cache.h"
#include "video_core/renderer_opengl/gl_rasterizer.h"
#include "video_core/renderer_opengl/gl_shader_decompiler.h"
@@ -15,30 +14,31 @@
namespace OpenGL {
-CachedGlobalRegion::CachedGlobalRegion(VAddr addr, u32 size) : addr{addr}, size{size} {
+CachedGlobalRegion::CachedGlobalRegion(VAddr cpu_addr, u8* host_ptr, u32 size, u32 max_size)
+ : RasterizerCacheObject{host_ptr}, cpu_addr{cpu_addr}, host_ptr{host_ptr}, size{size},
+ max_size{max_size} {
buffer.Create();
- // Bind and unbind the buffer so it gets allocated by the driver
- glBindBuffer(GL_SHADER_STORAGE_BUFFER, buffer.handle);
- glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
- LabelGLObject(GL_BUFFER, buffer.handle, addr, "GlobalMemory");
+ LabelGLObject(GL_BUFFER, buffer.handle, cpu_addr, "GlobalMemory");
}
-void CachedGlobalRegion::Reload(u32 size_) {
- constexpr auto max_size = static_cast<u32>(RasterizerOpenGL::MaxGlobalMemorySize);
+CachedGlobalRegion::~CachedGlobalRegion() = default;
+void CachedGlobalRegion::Reload(u32 size_) {
size = size_;
if (size > max_size) {
size = max_size;
- LOG_CRITICAL(HW_GPU, "Global region size {} exceeded the expected size {}!", size_,
+ LOG_CRITICAL(HW_GPU, "Global region size {} exceeded the supported size {}!", size_,
max_size);
}
+ glNamedBufferData(buffer.handle, size, host_ptr, GL_STREAM_DRAW);
+}
- // TODO(Rodrigo): Get rid of Memory::GetPointer with a staging buffer
- glBindBuffer(GL_SHADER_STORAGE_BUFFER, buffer.handle);
- glBufferData(GL_SHADER_STORAGE_BUFFER, size, Memory::GetPointer(addr), GL_DYNAMIC_DRAW);
+void CachedGlobalRegion::Flush() {
+ LOG_DEBUG(Render_OpenGL, "Flushing {} bytes to CPU memory address 0x{:16}", size, cpu_addr);
+ glGetNamedBufferSubData(buffer.handle, 0, static_cast<GLsizeiptr>(size), host_ptr);
}
-GlobalRegion GlobalRegionCacheOpenGL::TryGetReservedGlobalRegion(VAddr addr, u32 size) const {
+GlobalRegion GlobalRegionCacheOpenGL::TryGetReservedGlobalRegion(CacheAddr addr, u32 size) const {
const auto search{reserve.find(addr)};
if (search == reserve.end()) {
return {};
@@ -46,45 +46,52 @@ GlobalRegion GlobalRegionCacheOpenGL::TryGetReservedGlobalRegion(VAddr addr, u32
return search->second;
}
-GlobalRegion GlobalRegionCacheOpenGL::GetUncachedGlobalRegion(VAddr addr, u32 size) {
- GlobalRegion region{TryGetReservedGlobalRegion(addr, size)};
+GlobalRegion GlobalRegionCacheOpenGL::GetUncachedGlobalRegion(GPUVAddr addr, u8* host_ptr,
+ u32 size) {
+ GlobalRegion region{TryGetReservedGlobalRegion(ToCacheAddr(host_ptr), size)};
if (!region) {
// No reserved surface available, create a new one and reserve it
- region = std::make_shared<CachedGlobalRegion>(addr, size);
+ auto& memory_manager{Core::System::GetInstance().GPU().MemoryManager()};
+ const auto cpu_addr{memory_manager.GpuToCpuAddress(addr)};
+ ASSERT(cpu_addr);
+
+ region = std::make_shared<CachedGlobalRegion>(*cpu_addr, host_ptr, size, max_ssbo_size);
ReserveGlobalRegion(region);
}
region->Reload(size);
return region;
}
-void GlobalRegionCacheOpenGL::ReserveGlobalRegion(const GlobalRegion& region) {
- reserve[region->GetAddr()] = region;
+void GlobalRegionCacheOpenGL::ReserveGlobalRegion(GlobalRegion region) {
+ reserve.insert_or_assign(region->GetCacheAddr(), std::move(region));
}
GlobalRegionCacheOpenGL::GlobalRegionCacheOpenGL(RasterizerOpenGL& rasterizer)
- : RasterizerCache{rasterizer} {}
+ : RasterizerCache{rasterizer} {
+ GLint max_ssbo_size_;
+ glGetIntegerv(GL_MAX_SHADER_STORAGE_BLOCK_SIZE, &max_ssbo_size_);
+ max_ssbo_size = static_cast<u32>(max_ssbo_size_);
+}
GlobalRegion GlobalRegionCacheOpenGL::GetGlobalRegion(
const GLShader::GlobalMemoryEntry& global_region,
Tegra::Engines::Maxwell3D::Regs::ShaderStage stage) {
auto& gpu{Core::System::GetInstance().GPU()};
- const auto cbufs = gpu.Maxwell3D().state.shader_stages[static_cast<u64>(stage)];
- const auto cbuf_addr = gpu.MemoryManager().GpuToCpuAddress(
- cbufs.const_buffers[global_region.GetCbufIndex()].address + global_region.GetCbufOffset());
- ASSERT(cbuf_addr);
-
- const auto actual_addr_gpu = Memory::Read64(*cbuf_addr);
- const auto size = Memory::Read32(*cbuf_addr + 8);
- const auto actual_addr = gpu.MemoryManager().GpuToCpuAddress(actual_addr_gpu);
- ASSERT(actual_addr);
+ auto& memory_manager{gpu.MemoryManager()};
+ const auto cbufs{gpu.Maxwell3D().state.shader_stages[static_cast<std::size_t>(stage)]};
+ const auto addr{cbufs.const_buffers[global_region.GetCbufIndex()].address +
+ global_region.GetCbufOffset()};
+ const auto actual_addr{memory_manager.Read<u64>(addr)};
+ const auto size{memory_manager.Read<u32>(addr + 8)};
// Look up global region in the cache based on address
- GlobalRegion region = TryGet(*actual_addr);
+ const auto& host_ptr{memory_manager.GetPointer(actual_addr)};
+ GlobalRegion region{TryGet(host_ptr)};
if (!region) {
// No global region found - create a new one
- region = GetUncachedGlobalRegion(*actual_addr, size);
+ region = GetUncachedGlobalRegion(actual_addr, host_ptr, size);
Register(region);
}
diff --git a/src/video_core/renderer_opengl/gl_global_cache.h b/src/video_core/renderer_opengl/gl_global_cache.h
index 37830bb7c..2d467a240 100644
--- a/src/video_core/renderer_opengl/gl_global_cache.h
+++ b/src/video_core/renderer_opengl/gl_global_cache.h
@@ -19,7 +19,7 @@ namespace OpenGL {
namespace GLShader {
class GlobalMemoryEntry;
-} // namespace GLShader
+}
class RasterizerOpenGL;
class CachedGlobalRegion;
@@ -27,15 +27,14 @@ using GlobalRegion = std::shared_ptr<CachedGlobalRegion>;
class CachedGlobalRegion final : public RasterizerCacheObject {
public:
- explicit CachedGlobalRegion(VAddr addr, u32 size);
+ explicit CachedGlobalRegion(VAddr cpu_addr, u8* host_ptr, u32 size, u32 max_size);
+ ~CachedGlobalRegion();
- /// Gets the address of the shader in guest memory, required for cache management
- VAddr GetAddr() const {
- return addr;
+ VAddr GetCpuAddr() const override {
+ return cpu_addr;
}
- /// Gets the size of the shader in guest memory, required for cache management
- std::size_t GetSizeInBytes() const {
+ std::size_t GetSizeInBytes() const override {
return size;
}
@@ -47,14 +46,13 @@ public:
/// Reloads the global region from guest memory
void Reload(u32 size_);
- // TODO(Rodrigo): When global memory is written (STG), implement flushing
- void Flush() override {
- UNIMPLEMENTED();
- }
+ void Flush();
private:
- VAddr addr{};
+ VAddr cpu_addr{};
+ u8* host_ptr{};
u32 size{};
+ u32 max_size{};
OGLBuffer buffer;
};
@@ -67,12 +65,18 @@ public:
GlobalRegion GetGlobalRegion(const GLShader::GlobalMemoryEntry& descriptor,
Tegra::Engines::Maxwell3D::Regs::ShaderStage stage);
+protected:
+ void FlushObjectInner(const GlobalRegion& object) override {
+ object->Flush();
+ }
+
private:
- GlobalRegion TryGetReservedGlobalRegion(VAddr addr, u32 size) const;
- GlobalRegion GetUncachedGlobalRegion(VAddr addr, u32 size);
- void ReserveGlobalRegion(const GlobalRegion& region);
+ GlobalRegion TryGetReservedGlobalRegion(CacheAddr addr, u32 size) const;
+ GlobalRegion GetUncachedGlobalRegion(GPUVAddr addr, u8* host_ptr, u32 size);
+ void ReserveGlobalRegion(GlobalRegion region);
- std::unordered_map<VAddr, GlobalRegion> reserve;
+ std::unordered_map<CacheAddr, GlobalRegion> reserve;
+ u32 max_ssbo_size{};
};
} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_primitive_assembler.cpp b/src/video_core/renderer_opengl/gl_primitive_assembler.cpp
deleted file mode 100644
index 77d5cedd2..000000000
--- a/src/video_core/renderer_opengl/gl_primitive_assembler.cpp
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <algorithm>
-#include <array>
-#include "common/assert.h"
-#include "common/common_types.h"
-#include "core/core.h"
-#include "core/memory.h"
-#include "video_core/renderer_opengl/gl_buffer_cache.h"
-#include "video_core/renderer_opengl/gl_primitive_assembler.h"
-
-namespace OpenGL {
-
-constexpr u32 TRIANGLES_PER_QUAD = 6;
-constexpr std::array<u32, TRIANGLES_PER_QUAD> QUAD_MAP = {0, 1, 2, 0, 2, 3};
-
-PrimitiveAssembler::PrimitiveAssembler(OGLBufferCache& buffer_cache) : buffer_cache(buffer_cache) {}
-
-PrimitiveAssembler::~PrimitiveAssembler() = default;
-
-std::size_t PrimitiveAssembler::CalculateQuadSize(u32 count) const {
- ASSERT_MSG(count % 4 == 0, "Quad count is expected to be a multiple of 4");
- return (count / 4) * TRIANGLES_PER_QUAD * sizeof(GLuint);
-}
-
-GLintptr PrimitiveAssembler::MakeQuadArray(u32 first, u32 count) {
- const std::size_t size{CalculateQuadSize(count)};
- auto [dst_pointer, index_offset] = buffer_cache.ReserveMemory(size);
-
- for (u32 primitive = 0; primitive < count / 4; ++primitive) {
- for (u32 i = 0; i < TRIANGLES_PER_QUAD; ++i) {
- const u32 index = first + primitive * 4 + QUAD_MAP[i];
- std::memcpy(dst_pointer, &index, sizeof(index));
- dst_pointer += sizeof(index);
- }
- }
-
- return index_offset;
-}
-
-GLintptr PrimitiveAssembler::MakeQuadIndexed(Tegra::GPUVAddr gpu_addr, std::size_t index_size,
- u32 count) {
- const std::size_t map_size{CalculateQuadSize(count)};
- auto [dst_pointer, index_offset] = buffer_cache.ReserveMemory(map_size);
-
- auto& memory_manager = Core::System::GetInstance().GPU().MemoryManager();
- const auto cpu_addr{memory_manager.GpuToCpuAddress(gpu_addr)};
- ASSERT_MSG(cpu_addr, "Invalid GPU address");
-
- const u8* source{Memory::GetPointer(*cpu_addr)};
-
- for (u32 primitive = 0; primitive < count / 4; ++primitive) {
- for (std::size_t i = 0; i < TRIANGLES_PER_QUAD; ++i) {
- const u32 index = primitive * 4 + QUAD_MAP[i];
- const u8* src_offset = source + (index * index_size);
-
- std::memcpy(dst_pointer, src_offset, index_size);
- dst_pointer += index_size;
- }
- }
-
- return index_offset;
-}
-
-} // namespace OpenGL \ No newline at end of file
diff --git a/src/video_core/renderer_opengl/gl_primitive_assembler.h b/src/video_core/renderer_opengl/gl_primitive_assembler.h
deleted file mode 100644
index a8cb88eb5..000000000
--- a/src/video_core/renderer_opengl/gl_primitive_assembler.h
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <vector>
-#include <glad/glad.h>
-
-#include "common/common_types.h"
-#include "video_core/memory_manager.h"
-
-namespace OpenGL {
-
-class OGLBufferCache;
-
-class PrimitiveAssembler {
-public:
- explicit PrimitiveAssembler(OGLBufferCache& buffer_cache);
- ~PrimitiveAssembler();
-
- /// Calculates the size required by MakeQuadArray and MakeQuadIndexed.
- std::size_t CalculateQuadSize(u32 count) const;
-
- GLintptr MakeQuadArray(u32 first, u32 count);
-
- GLintptr MakeQuadIndexed(Tegra::GPUVAddr gpu_addr, std::size_t index_size, u32 count);
-
-private:
- OGLBufferCache& buffer_cache;
-};
-
-} // namespace OpenGL \ No newline at end of file
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 168288088..d77426067 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -17,7 +17,6 @@
#include "common/microprofile.h"
#include "common/scope_exit.h"
#include "core/core.h"
-#include "core/frontend/emu_window.h"
#include "core/hle/kernel/process.h"
#include "core/settings.h"
#include "video_core/engines/maxwell_3d.h"
@@ -26,7 +25,6 @@
#include "video_core/renderer_opengl/gl_shader_gen.h"
#include "video_core/renderer_opengl/maxwell_to_gl.h"
#include "video_core/renderer_opengl/renderer_opengl.h"
-#include "video_core/video_core.h"
namespace OpenGL {
@@ -100,25 +98,18 @@ struct FramebufferCacheKey {
}
};
-RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& window, Core::System& system,
+RasterizerOpenGL::RasterizerOpenGL(Core::System& system, Core::Frontend::EmuWindow& emu_window,
ScreenInfo& info)
- : res_cache{*this}, shader_cache{*this, system}, global_cache{*this}, emu_window{window},
- screen_info{info}, buffer_cache(*this, STREAM_BUFFER_SIZE) {
- // Create sampler objects
- for (std::size_t i = 0; i < texture_samplers.size(); ++i) {
- texture_samplers[i].Create();
- state.texture_units[i].sampler = texture_samplers[i].sampler.handle;
- }
-
+ : res_cache{*this}, shader_cache{*this, system, emu_window, device},
+ global_cache{*this}, system{system}, screen_info{info},
+ buffer_cache(*this, STREAM_BUFFER_SIZE) {
OpenGLState::ApplyDefaultState();
shader_program_manager = std::make_unique<GLShader::ProgramManager>();
state.draw.shader_program = 0;
state.Apply();
- glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &uniform_buffer_alignment);
-
- LOG_CRITICAL(Render_OpenGL, "Sync fixed function OpenGL state here!");
+ LOG_DEBUG(Render_OpenGL, "Sync fixed function OpenGL state here");
CheckExtensions();
}
@@ -138,7 +129,7 @@ void RasterizerOpenGL::CheckExtensions() {
}
GLuint RasterizerOpenGL::SetupVertexFormat() {
- auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
+ auto& gpu = system.GPU().Maxwell3D();
const auto& regs = gpu.regs;
if (!gpu.dirty_flags.vertex_attrib_format) {
@@ -177,7 +168,7 @@ GLuint RasterizerOpenGL::SetupVertexFormat() {
continue;
const auto& buffer = regs.vertex_array[attrib.buffer];
- LOG_TRACE(HW_GPU,
+ LOG_TRACE(Render_OpenGL,
"vertex attrib {}, count={}, size={}, type={}, offset={}, normalize={}",
index, attrib.ComponentCount(), attrib.SizeString(), attrib.TypeString(),
attrib.offset.Value(), attrib.IsNormalized());
@@ -207,7 +198,7 @@ GLuint RasterizerOpenGL::SetupVertexFormat() {
}
void RasterizerOpenGL::SetupVertexBuffer(GLuint vao) {
- auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
+ auto& gpu = system.GPU().Maxwell3D();
const auto& regs = gpu.regs;
if (gpu.dirty_flags.vertex_array.none())
@@ -224,8 +215,8 @@ void RasterizerOpenGL::SetupVertexBuffer(GLuint vao) {
if (!vertex_array.IsEnabled())
continue;
- const Tegra::GPUVAddr start = vertex_array.StartAddress();
- const Tegra::GPUVAddr end = regs.vertex_array_limit[index].LimitAddress();
+ const GPUVAddr start = vertex_array.StartAddress();
+ const GPUVAddr end = regs.vertex_array_limit[index].LimitAddress();
ASSERT(end > start);
const u64 size = end - start + 1;
@@ -248,36 +239,13 @@ void RasterizerOpenGL::SetupVertexBuffer(GLuint vao) {
}
DrawParameters RasterizerOpenGL::SetupDraw() {
- const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
+ const auto& gpu = system.GPU().Maxwell3D();
const auto& regs = gpu.regs;
const bool is_indexed = accelerate_draw == AccelDraw::Indexed;
DrawParameters params{};
params.current_instance = gpu.state.current_instance;
- if (regs.draw.topology == Maxwell::PrimitiveTopology::Quads) {
- MICROPROFILE_SCOPE(OpenGL_PrimitiveAssembly);
-
- params.use_indexed = true;
- params.primitive_mode = GL_TRIANGLES;
-
- if (is_indexed) {
- params.index_format = MaxwellToGL::IndexFormat(regs.index_array.format);
- params.count = (regs.index_array.count / 4) * 6;
- params.index_buffer_offset = primitive_assembler.MakeQuadIndexed(
- regs.index_array.IndexStart(), regs.index_array.FormatSizeInBytes(),
- regs.index_array.count);
- params.base_vertex = static_cast<GLint>(regs.vb_element_base);
- } else {
- // MakeQuadArray always generates u32 indexes
- params.index_format = GL_UNSIGNED_INT;
- params.count = (regs.vertex_buffer.count / 4) * 6;
- params.index_buffer_offset =
- primitive_assembler.MakeQuadArray(regs.vertex_buffer.first, params.count);
- }
- return params;
- }
-
params.use_indexed = is_indexed;
params.primitive_mode = MaxwellToGL::PrimitiveTopology(regs.draw.topology);
@@ -297,11 +265,15 @@ DrawParameters RasterizerOpenGL::SetupDraw() {
void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) {
MICROPROFILE_SCOPE(OpenGL_Shader);
- auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
+ auto& gpu = system.GPU().Maxwell3D();
BaseBindings base_bindings;
std::array<bool, Maxwell::NumClipDistances> clip_distances{};
+ // Prepare packed bindings
+ bind_ubo_pushbuffer.Setup(base_bindings.cbuf);
+ bind_ssbo_pushbuffer.Setup(base_bindings.gmem);
+
for (std::size_t index = 0; index < Maxwell::MaxShaderProgram; ++index) {
const auto& shader_config = gpu.regs.shader_config[index];
const Maxwell::ShaderProgram program{static_cast<Maxwell::ShaderProgram>(index)};
@@ -312,6 +284,8 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) {
case Maxwell::ShaderProgram::Geometry:
shader_program_manager->UseTrivialGeometryShader();
break;
+ default:
+ break;
}
continue;
}
@@ -319,13 +293,13 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) {
const std::size_t stage{index == 0 ? 0 : index - 1}; // Stage indices are 0 - 5
GLShader::MaxwellUniformData ubo{};
- ubo.SetFromRegs(gpu.state.shader_stages[stage]);
- const GLintptr offset = buffer_cache.UploadHostMemory(
- &ubo, sizeof(ubo), static_cast<std::size_t>(uniform_buffer_alignment));
+ ubo.SetFromRegs(gpu, stage);
+ const GLintptr offset =
+ buffer_cache.UploadHostMemory(&ubo, sizeof(ubo), device.GetUniformBufferAlignment());
// Bind the emulation info buffer
- glBindBufferRange(GL_UNIFORM_BUFFER, base_bindings.cbuf, buffer_cache.GetHandle(), offset,
- static_cast<GLsizeiptr>(sizeof(ubo)));
+ bind_ubo_pushbuffer.Push(buffer_cache.GetHandle(), offset,
+ static_cast<GLsizeiptr>(sizeof(ubo)));
Shader shader{shader_cache.GetStageProgram(program)};
const auto [program_handle, next_bindings] =
@@ -343,15 +317,14 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) {
shader_program_manager->UseProgrammableFragmentShader(program_handle);
break;
default:
- LOG_CRITICAL(HW_GPU, "Unimplemented shader index={}, enable={}, offset=0x{:08X}", index,
- shader_config.enable.Value(), shader_config.offset);
- UNREACHABLE();
+ UNIMPLEMENTED_MSG("Unimplemented shader index={}, enable={}, offset=0x{:08X}", index,
+ shader_config.enable.Value(), shader_config.offset);
}
const auto stage_enum = static_cast<Maxwell::ShaderStage>(stage);
- SetupConstBuffers(stage_enum, shader, program_handle, base_bindings);
- SetupGlobalRegions(stage_enum, shader, program_handle, base_bindings);
- SetupTextures(stage_enum, shader, program_handle, base_bindings);
+ SetupDrawConstBuffers(stage_enum, shader);
+ SetupGlobalRegions(stage_enum, shader);
+ SetupTextures(stage_enum, shader, base_bindings);
// Workaround for Intel drivers.
// When a clip distance is enabled but not set in the shader it crops parts of the screen
@@ -370,6 +343,9 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) {
base_bindings = next_bindings;
}
+ bind_ubo_pushbuffer.Bind();
+ bind_ssbo_pushbuffer.Bind();
+
SyncClipEnabled(clip_distances);
gpu.dirty_flags.shaders = false;
@@ -414,15 +390,15 @@ void RasterizerOpenGL::SetupCachedFramebuffer(const FramebufferCacheKey& fbkey,
}
std::size_t RasterizerOpenGL::CalculateVertexArraysSize() const {
- const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
+ 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 Tegra::GPUVAddr start = regs.vertex_array[index].StartAddress();
- const Tegra::GPUVAddr end = regs.vertex_array_limit[index].LimitAddress();
+ const GPUVAddr start = regs.vertex_array[index].StartAddress();
+ const GPUVAddr end = regs.vertex_array_limit[index].LimitAddress();
ASSERT(end > start);
size += end - start + 1;
@@ -432,7 +408,7 @@ std::size_t RasterizerOpenGL::CalculateVertexArraysSize() const {
}
std::size_t RasterizerOpenGL::CalculateIndexBufferSize() const {
- const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
+ 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());
@@ -449,7 +425,7 @@ static constexpr auto RangeFromInterval(Map& map, const Interval& interval) {
return boost::make_iterator_range(map.equal_range(interval));
}
-void RasterizerOpenGL::UpdatePagesCachedCount(Tegra::GPUVAddr addr, u64 size, int delta) {
+void RasterizerOpenGL::UpdatePagesCachedCount(VAddr addr, u64 size, int delta) {
const u64 page_start{addr >> Memory::PAGE_BITS};
const u64 page_end{(addr + size + Memory::PAGE_SIZE - 1) >> Memory::PAGE_BITS};
@@ -488,7 +464,7 @@ std::pair<bool, bool> RasterizerOpenGL::ConfigureFramebuffers(
OpenGLState& current_state, bool using_color_fb, bool using_depth_fb, bool preserve_contents,
std::optional<std::size_t> single_color_target) {
MICROPROFILE_SCOPE(OpenGL_Framebuffer);
- auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
+ auto& gpu = system.GPU().Maxwell3D();
const auto& regs = gpu.regs;
const FramebufferConfigState fb_config_state{using_color_fb, using_depth_fb, preserve_contents,
@@ -579,10 +555,7 @@ std::pair<bool, bool> RasterizerOpenGL::ConfigureFramebuffers(
}
void RasterizerOpenGL::Clear() {
- const auto prev_state{state};
- SCOPE_EXIT({ prev_state.Apply(); });
-
- const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
+ const auto& regs = system.GPU().Maxwell3D().regs;
bool use_color{};
bool use_depth{};
bool use_stencil{};
@@ -653,7 +626,10 @@ void RasterizerOpenGL::Clear() {
clear_state.EmulateViewportWithScissor();
}
- clear_state.Apply();
+ clear_state.ApplyColorMask();
+ clear_state.ApplyDepth();
+ clear_state.ApplyStencilTest();
+ clear_state.ApplyViewport();
if (use_color) {
glClearBufferfv(GL_COLOR, regs.clear_buffers.RT, regs.clear_color);
@@ -673,7 +649,7 @@ void RasterizerOpenGL::DrawArrays() {
return;
MICROPROFILE_SCOPE(OpenGL_Drawing);
- auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
+ auto& gpu = system.GPU().Maxwell3D();
const auto& regs = gpu.regs;
ConfigureFramebuffers(state);
@@ -687,39 +663,29 @@ void RasterizerOpenGL::DrawArrays() {
SyncCullMode();
SyncPrimitiveRestart();
SyncScissorTest(state);
- // Alpha Testing is synced on shaders.
SyncTransformFeedback();
SyncPointState();
- CheckAlphaTests();
SyncPolygonOffset();
- // TODO(bunnei): Sync framebuffer_scale uniform here
- // TODO(bunnei): Sync scissorbox uniform(s) here
+ SyncAlphaTest();
// Draw the vertex batch
const bool is_indexed = accelerate_draw == AccelDraw::Indexed;
std::size_t buffer_size = CalculateVertexArraysSize();
- // Add space for index buffer (keeping in mind non-core primitives)
- switch (regs.draw.topology) {
- case Maxwell::PrimitiveTopology::Quads:
- buffer_size = Common::AlignUp<std::size_t>(buffer_size, 4) +
- primitive_assembler.CalculateQuadSize(regs.vertex_buffer.count);
- break;
- default:
- if (is_indexed) {
- buffer_size = Common::AlignUp<std::size_t>(buffer_size, 4) + CalculateIndexBufferSize();
- }
- break;
+ // 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(GLShader::MaxwellUniformData) + uniform_buffer_alignment) * Maxwell::MaxShaderStage;
+ buffer_size = Common::AlignUp<std::size_t>(buffer_size, 4) +
+ (sizeof(GLShader::MaxwellUniformData) + device.GetUniformBufferAlignment()) *
+ Maxwell::MaxShaderStage;
// Add space for at least 18 constant buffers
- buffer_size += Maxwell::MaxConstBuffers * (MaxConstbufferSize + uniform_buffer_alignment);
+ buffer_size +=
+ Maxwell::MaxConstBuffers * (MaxConstbufferSize + device.GetUniformBufferAlignment());
const bool invalidate = buffer_cache.Map(buffer_size);
if (invalidate) {
@@ -747,20 +713,27 @@ void RasterizerOpenGL::DrawArrays() {
void RasterizerOpenGL::FlushAll() {}
-void RasterizerOpenGL::FlushRegion(VAddr addr, u64 size) {
+void RasterizerOpenGL::FlushRegion(CacheAddr addr, u64 size) {
MICROPROFILE_SCOPE(OpenGL_CacheManagement);
+ if (!addr || !size) {
+ return;
+ }
res_cache.FlushRegion(addr, size);
+ global_cache.FlushRegion(addr, size);
}
-void RasterizerOpenGL::InvalidateRegion(VAddr addr, u64 size) {
+void RasterizerOpenGL::InvalidateRegion(CacheAddr addr, u64 size) {
MICROPROFILE_SCOPE(OpenGL_CacheManagement);
+ if (!addr || !size) {
+ return;
+ }
res_cache.InvalidateRegion(addr, size);
shader_cache.InvalidateRegion(addr, size);
global_cache.InvalidateRegion(addr, size);
buffer_cache.InvalidateRegion(addr, size);
}
-void RasterizerOpenGL::FlushAndInvalidateRegion(VAddr addr, u64 size) {
+void RasterizerOpenGL::FlushAndInvalidateRegion(CacheAddr addr, u64 size) {
FlushRegion(addr, size);
InvalidateRegion(addr, size);
}
@@ -782,7 +755,7 @@ bool RasterizerOpenGL::AccelerateDisplay(const Tegra::FramebufferConfig& config,
MICROPROFILE_SCOPE(OpenGL_CacheManagement);
- const auto& surface{res_cache.TryFindFramebufferSurface(framebuffer_addr)};
+ const auto& surface{res_cache.TryFindFramebufferSurface(Memory::GetPointer(framebuffer_addr))};
if (!surface) {
return {};
}
@@ -793,200 +766,81 @@ bool RasterizerOpenGL::AccelerateDisplay(const Tegra::FramebufferConfig& config,
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");
- ASSERT_MSG(params.pixel_format == pixel_format, "Framebuffer pixel_format is different");
- screen_info.display_texture = surface->Texture().handle;
-
- return true;
-}
-
-void RasterizerOpenGL::SamplerInfo::Create() {
- sampler.Create();
- mag_filter = min_filter = Tegra::Texture::TextureFilter::Linear;
- wrap_u = wrap_v = wrap_p = Tegra::Texture::WrapMode::Wrap;
- uses_depth_compare = false;
- depth_compare_func = Tegra::Texture::DepthCompareFunc::Never;
-
- // default is GL_LINEAR_MIPMAP_LINEAR
- glSamplerParameteri(sampler.handle, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
- // Other attributes have correct defaults
- glSamplerParameteri(sampler.handle, GL_TEXTURE_COMPARE_FUNC, GL_NEVER);
-}
-
-void RasterizerOpenGL::SamplerInfo::SyncWithConfig(const Tegra::Texture::TSCEntry& config) {
- const GLuint s = sampler.handle;
- if (mag_filter != config.mag_filter) {
- mag_filter = config.mag_filter;
- glSamplerParameteri(
- s, GL_TEXTURE_MAG_FILTER,
- MaxwellToGL::TextureFilterMode(mag_filter, Tegra::Texture::TextureMipmapFilter::None));
- }
- if (min_filter != config.min_filter || mip_filter != config.mip_filter) {
- min_filter = config.min_filter;
- mip_filter = config.mip_filter;
- glSamplerParameteri(s, GL_TEXTURE_MIN_FILTER,
- MaxwellToGL::TextureFilterMode(min_filter, mip_filter));
+ if (params.pixel_format != pixel_format) {
+ LOG_WARNING(Render_OpenGL, "Framebuffer pixel_format is different");
}
- if (wrap_u != config.wrap_u) {
- wrap_u = config.wrap_u;
- glSamplerParameteri(s, GL_TEXTURE_WRAP_S, MaxwellToGL::WrapMode(wrap_u));
- }
- if (wrap_v != config.wrap_v) {
- wrap_v = config.wrap_v;
- glSamplerParameteri(s, GL_TEXTURE_WRAP_T, MaxwellToGL::WrapMode(wrap_v));
- }
- if (wrap_p != config.wrap_p) {
- wrap_p = config.wrap_p;
- glSamplerParameteri(s, GL_TEXTURE_WRAP_R, MaxwellToGL::WrapMode(wrap_p));
- }
-
- if (uses_depth_compare != (config.depth_compare_enabled == 1)) {
- uses_depth_compare = (config.depth_compare_enabled == 1);
- if (uses_depth_compare) {
- glSamplerParameteri(s, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
- } else {
- glSamplerParameteri(s, GL_TEXTURE_COMPARE_MODE, GL_NONE);
- }
- }
-
- if (depth_compare_func != config.depth_compare_func) {
- depth_compare_func = config.depth_compare_func;
- glSamplerParameteri(s, GL_TEXTURE_COMPARE_FUNC,
- MaxwellToGL::DepthCompareFunc(depth_compare_func));
- }
-
- GLvec4 new_border_color;
- if (config.srgb_conversion) {
- new_border_color[0] = config.srgb_border_color_r / 255.0f;
- new_border_color[1] = config.srgb_border_color_g / 255.0f;
- new_border_color[2] = config.srgb_border_color_g / 255.0f;
- } else {
- new_border_color[0] = config.border_color_r;
- new_border_color[1] = config.border_color_g;
- new_border_color[2] = config.border_color_b;
- }
- new_border_color[3] = config.border_color_a;
-
- if (border_color != new_border_color) {
- border_color = new_border_color;
- glSamplerParameterfv(s, GL_TEXTURE_BORDER_COLOR, border_color.data());
- }
-
- const float anisotropic_max = static_cast<float>(1 << config.max_anisotropy.Value());
- if (anisotropic_max != max_anisotropic) {
- max_anisotropic = anisotropic_max;
- if (GLAD_GL_ARB_texture_filter_anisotropic) {
- glSamplerParameterf(s, GL_TEXTURE_MAX_ANISOTROPY, max_anisotropic);
- } else if (GLAD_GL_EXT_texture_filter_anisotropic) {
- glSamplerParameterf(s, GL_TEXTURE_MAX_ANISOTROPY_EXT, max_anisotropic);
- }
- }
- const float lod_min = static_cast<float>(config.min_lod_clamp.Value()) / 256.0f;
- if (lod_min != min_lod) {
- min_lod = lod_min;
- glSamplerParameterf(s, GL_TEXTURE_MIN_LOD, min_lod);
- }
+ screen_info.display_texture = surface->Texture().handle;
- const float lod_max = static_cast<float>(config.max_lod_clamp.Value()) / 256.0f;
- if (lod_max != max_lod) {
- max_lod = lod_max;
- glSamplerParameterf(s, GL_TEXTURE_MAX_LOD, max_lod);
- }
- const u32 bias = config.mip_lod_bias.Value();
- // Sign extend the 13-bit value.
- constexpr u32 mask = 1U << (13 - 1);
- const float bias_lod = static_cast<s32>((bias ^ mask) - mask) / 256.f;
- if (lod_bias != bias_lod) {
- lod_bias = bias_lod;
- glSamplerParameterf(s, GL_TEXTURE_LOD_BIAS, lod_bias);
- }
+ return true;
}
-void RasterizerOpenGL::SetupConstBuffers(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage,
- const Shader& shader, GLuint program_handle,
- BaseBindings base_bindings) {
+void RasterizerOpenGL::SetupDrawConstBuffers(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage,
+ const Shader& shader) {
MICROPROFILE_SCOPE(OpenGL_UBO);
- const auto& gpu = Core::System::GetInstance().GPU();
- const auto& maxwell3d = gpu.Maxwell3D();
- const auto& shader_stage = maxwell3d.state.shader_stages[static_cast<std::size_t>(stage)];
+ const auto stage_index = static_cast<std::size_t>(stage);
+ const auto& shader_stage = system.GPU().Maxwell3D().state.shader_stages[stage_index];
const auto& entries = shader->GetShaderEntries().const_buffers;
- constexpr u64 max_binds = Tegra::Engines::Maxwell3D::Regs::MaxConstBuffers;
- std::array<GLuint, max_binds> bind_buffers;
- std::array<GLintptr, max_binds> bind_offsets;
- std::array<GLsizeiptr, max_binds> bind_sizes;
-
- ASSERT_MSG(entries.size() <= max_binds, "Exceeded expected number of binding points.");
-
// Upload only the enabled buffers from the 16 constbuffers of each shader stage
for (u32 bindpoint = 0; bindpoint < entries.size(); ++bindpoint) {
- const auto& used_buffer = entries[bindpoint];
- const auto& buffer = shader_stage.const_buffers[used_buffer.GetIndex()];
-
- if (!buffer.enabled) {
- // With disabled buffers set values as zero to unbind them
- bind_buffers[bindpoint] = 0;
- bind_offsets[bindpoint] = 0;
- bind_sizes[bindpoint] = 0;
- continue;
- }
+ const auto& entry = entries[bindpoint];
+ SetupConstBuffer(shader_stage.const_buffers[entry.GetIndex()], entry);
+ }
+}
- std::size_t size = 0;
+void RasterizerOpenGL::SetupConstBuffer(const Tegra::Engines::ConstBufferInfo& buffer,
+ const GLShader::ConstBufferEntry& entry) {
+ if (!buffer.enabled) {
+ // Set values to zero to unbind buffers
+ bind_ubo_pushbuffer.Push(0, 0, 0);
+ return;
+ }
- if (used_buffer.IsIndirect()) {
- // Buffer is accessed indirectly, so upload the entire thing
- size = buffer.size;
+ std::size_t size;
+ if (entry.IsIndirect()) {
+ // Buffer is accessed indirectly, so upload the entire thing
+ size = buffer.size;
- if (size > MaxConstbufferSize) {
- LOG_CRITICAL(HW_GPU, "indirect constbuffer size {} exceeds maximum {}", size,
- MaxConstbufferSize);
- size = MaxConstbufferSize;
- }
- } else {
- // Buffer is accessed directly, upload just what we use
- size = used_buffer.GetSize();
+ if (size > MaxConstbufferSize) {
+ LOG_WARNING(Render_OpenGL, "Indirect constbuffer size {} exceeds maximum {}", size,
+ MaxConstbufferSize);
+ size = MaxConstbufferSize;
}
-
- // Align the actual size so it ends up being a multiple of vec4 to meet the OpenGL std140
- // UBO alignment requirements.
- size = Common::AlignUp(size, sizeof(GLvec4));
- ASSERT_MSG(size <= MaxConstbufferSize, "Constbuffer too big");
-
- const GLintptr const_buffer_offset = buffer_cache.UploadMemory(
- buffer.address, size, static_cast<std::size_t>(uniform_buffer_alignment));
-
- // Prepare values for multibind
- bind_buffers[bindpoint] = buffer_cache.GetHandle();
- bind_offsets[bindpoint] = const_buffer_offset;
- bind_sizes[bindpoint] = size;
+ } else {
+ // Buffer is accessed directly, upload just what we use
+ size = entry.GetSize();
}
- // The first binding is reserved for emulation values
- const GLuint ubo_base_binding = base_bindings.cbuf + 1;
- glBindBuffersRange(GL_UNIFORM_BUFFER, ubo_base_binding, static_cast<GLsizei>(entries.size()),
- bind_buffers.data(), bind_offsets.data(), bind_sizes.data());
+ // Align the actual size so it ends up being a multiple of vec4 to meet the OpenGL std140
+ // UBO alignment requirements.
+ size = Common::AlignUp(size, sizeof(GLvec4));
+ ASSERT_MSG(size <= MaxConstbufferSize, "Constant buffer is too big");
+
+ const std::size_t alignment = device.GetUniformBufferAlignment();
+ const GLintptr offset = buffer_cache.UploadMemory(buffer.address, size, alignment);
+ bind_ubo_pushbuffer.Push(buffer_cache.GetHandle(), offset, size);
}
void RasterizerOpenGL::SetupGlobalRegions(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage,
- const Shader& shader, GLenum primitive_mode,
- BaseBindings base_bindings) {
- // TODO(Rodrigo): Use ARB_multi_bind here
+ const Shader& shader) {
const auto& entries = shader->GetShaderEntries().global_memory_entries;
-
- for (u32 bindpoint = 0; bindpoint < static_cast<u32>(entries.size()); ++bindpoint) {
- const auto& entry = entries[bindpoint];
- const u32 current_bindpoint = base_bindings.gmem + bindpoint;
- const auto& region = global_cache.GetGlobalRegion(entry, stage);
-
- glBindBufferBase(GL_SHADER_STORAGE_BUFFER, current_bindpoint, region->GetBufferHandle());
+ for (std::size_t bindpoint = 0; bindpoint < entries.size(); ++bindpoint) {
+ const auto& entry{entries[bindpoint]};
+ const auto& region{global_cache.GetGlobalRegion(entry, stage)};
+ if (entry.IsWritten()) {
+ region->MarkAsModified(true, global_cache);
+ }
+ bind_ssbo_pushbuffer.Push(region->GetBufferHandle(), 0,
+ static_cast<GLsizeiptr>(region->GetSizeInBytes()));
}
}
void RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, const Shader& shader,
- GLuint program_handle, BaseBindings base_bindings) {
+ BaseBindings base_bindings) {
MICROPROFILE_SCOPE(OpenGL_Texture);
- const auto& gpu = Core::System::GetInstance().GPU();
+ const auto& gpu = system.GPU();
const auto& maxwell3d = gpu.Maxwell3D();
const auto& entries = shader->GetShaderEntries().samplers;
@@ -995,15 +849,22 @@ void RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, const Shader& s
for (u32 bindpoint = 0; bindpoint < entries.size(); ++bindpoint) {
const auto& entry = entries[bindpoint];
- const auto texture = maxwell3d.GetStageTexture(stage, entry.GetOffset());
+ Tegra::Texture::FullTextureInfo texture;
+ if (entry.IsBindless()) {
+ const auto cbuf = entry.GetBindlessCBuf();
+ Tegra::Texture::TextureHandle tex_handle;
+ tex_handle.raw = maxwell3d.AccessConstBuffer32(stage, cbuf.first, cbuf.second);
+ texture = maxwell3d.GetTextureInfo(tex_handle, entry.GetOffset());
+ } else {
+ texture = maxwell3d.GetStageTexture(stage, entry.GetOffset());
+ }
const u32 current_bindpoint = base_bindings.sampler + bindpoint;
- texture_samplers[current_bindpoint].SyncWithConfig(texture.tsc);
+ state.texture_units[current_bindpoint].sampler = sampler_cache.GetSampler(texture.tsc);
- Surface surface = res_cache.GetTextureSurface(texture, entry);
- if (surface != nullptr) {
+ if (Surface surface = res_cache.GetTextureSurface(texture, entry); surface) {
state.texture_units[current_bindpoint].texture =
- entry.IsArray() ? surface->TextureLayer().handle : surface->Texture().handle;
+ surface->Texture(entry.IsArray()).handle;
surface->UpdateSwizzle(texture.tic.x_source, texture.tic.y_source, texture.tic.z_source,
texture.tic.w_source);
} else {
@@ -1014,7 +875,7 @@ void RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, const Shader& s
}
void RasterizerOpenGL::SyncViewport(OpenGLState& current_state) {
- const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
+ const auto& regs = system.GPU().Maxwell3D().regs;
const bool geometry_shaders_enabled =
regs.IsShaderConfigEnabled(static_cast<size_t>(Maxwell::ShaderProgram::Geometry));
const std::size_t viewport_count =
@@ -1027,8 +888,8 @@ void RasterizerOpenGL::SyncViewport(OpenGLState& current_state) {
viewport.y = viewport_rect.bottom;
viewport.width = viewport_rect.GetWidth();
viewport.height = viewport_rect.GetHeight();
- viewport.depth_range_far = regs.viewports[i].depth_range_far;
- viewport.depth_range_near = regs.viewports[i].depth_range_near;
+ viewport.depth_range_far = src.depth_range_far;
+ viewport.depth_range_near = src.depth_range_near;
}
state.depth_clamp.far_plane = regs.view_volume_clip_control.depth_clamp_far != 0;
state.depth_clamp.near_plane = regs.view_volume_clip_control.depth_clamp_near != 0;
@@ -1037,7 +898,7 @@ void RasterizerOpenGL::SyncViewport(OpenGLState& current_state) {
void RasterizerOpenGL::SyncClipEnabled(
const std::array<bool, Maxwell::Regs::NumClipDistances>& clip_mask) {
- const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
+ const auto& regs = system.GPU().Maxwell3D().regs;
const std::array<bool, Maxwell::Regs::NumClipDistances> reg_state{
regs.clip_distance_enabled.c0 != 0, regs.clip_distance_enabled.c1 != 0,
regs.clip_distance_enabled.c2 != 0, regs.clip_distance_enabled.c3 != 0,
@@ -1054,7 +915,7 @@ void RasterizerOpenGL::SyncClipCoef() {
}
void RasterizerOpenGL::SyncCullMode() {
- const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
+ const auto& regs = system.GPU().Maxwell3D().regs;
state.cull.enabled = regs.cull.enabled != 0;
@@ -1078,14 +939,14 @@ void RasterizerOpenGL::SyncCullMode() {
}
void RasterizerOpenGL::SyncPrimitiveRestart() {
- const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
+ const auto& regs = system.GPU().Maxwell3D().regs;
state.primitive_restart.enabled = regs.primitive_restart.enabled;
state.primitive_restart.index = regs.primitive_restart.index;
}
void RasterizerOpenGL::SyncDepthTestState() {
- const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
+ const auto& regs = system.GPU().Maxwell3D().regs;
state.depth.test_enabled = regs.depth_test_enable != 0;
state.depth.write_mask = regs.depth_write_enabled ? GL_TRUE : GL_FALSE;
@@ -1097,7 +958,7 @@ void RasterizerOpenGL::SyncDepthTestState() {
}
void RasterizerOpenGL::SyncStencilTestState() {
- const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
+ const auto& regs = system.GPU().Maxwell3D().regs;
state.stencil.test_enabled = regs.stencil_enable != 0;
if (!regs.stencil_enable) {
@@ -1131,7 +992,7 @@ void RasterizerOpenGL::SyncStencilTestState() {
}
void RasterizerOpenGL::SyncColorMask() {
- const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
+ const auto& regs = system.GPU().Maxwell3D().regs;
const std::size_t count =
regs.independent_blend_enable ? Tegra::Engines::Maxwell3D::Regs::NumRenderTargets : 1;
for (std::size_t i = 0; i < count; i++) {
@@ -1145,18 +1006,18 @@ void RasterizerOpenGL::SyncColorMask() {
}
void RasterizerOpenGL::SyncMultiSampleState() {
- const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
+ const auto& regs = system.GPU().Maxwell3D().regs;
state.multisample_control.alpha_to_coverage = regs.multisample_control.alpha_to_coverage != 0;
state.multisample_control.alpha_to_one = regs.multisample_control.alpha_to_one != 0;
}
void RasterizerOpenGL::SyncFragmentColorClampState() {
- const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
+ const auto& regs = system.GPU().Maxwell3D().regs;
state.fragment_color_clamp.enabled = regs.frag_color_clamp != 0;
}
void RasterizerOpenGL::SyncBlendState() {
- const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
+ const auto& regs = system.GPU().Maxwell3D().regs;
state.blend_color.red = regs.blend_color.r;
state.blend_color.green = regs.blend_color.g;
@@ -1198,7 +1059,7 @@ void RasterizerOpenGL::SyncBlendState() {
}
void RasterizerOpenGL::SyncLogicOpState() {
- const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
+ const auto& regs = system.GPU().Maxwell3D().regs;
state.logic_op.enabled = regs.logic_op.enable != 0;
@@ -1212,7 +1073,7 @@ void RasterizerOpenGL::SyncLogicOpState() {
}
void RasterizerOpenGL::SyncScissorTest(OpenGLState& current_state) {
- const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
+ const auto& regs = system.GPU().Maxwell3D().regs;
const bool geometry_shaders_enabled =
regs.IsShaderConfigEnabled(static_cast<size_t>(Maxwell::ShaderProgram::Geometry));
const std::size_t viewport_count =
@@ -1234,21 +1095,19 @@ void RasterizerOpenGL::SyncScissorTest(OpenGLState& current_state) {
}
void RasterizerOpenGL::SyncTransformFeedback() {
- const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
-
- if (regs.tfb_enabled != 0) {
- LOG_CRITICAL(Render_OpenGL, "Transform feedbacks are not implemented");
- UNREACHABLE();
- }
+ const auto& regs = system.GPU().Maxwell3D().regs;
+ UNIMPLEMENTED_IF_MSG(regs.tfb_enabled != 0, "Transform feedbacks are not implemented");
}
void RasterizerOpenGL::SyncPointState() {
- const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
- state.point.size = regs.point_size;
+ const auto& regs = system.GPU().Maxwell3D().regs;
+ // Limit the point size to 1 since nouveau sometimes sets a point size of 0 (and that's invalid
+ // in OpenGL).
+ state.point.size = std::max(1.0f, regs.point_size);
}
void RasterizerOpenGL::SyncPolygonOffset() {
- const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
+ const auto& regs = system.GPU().Maxwell3D().regs;
state.polygon_offset.fill_enable = regs.polygon_offset_fill_enable != 0;
state.polygon_offset.line_enable = regs.polygon_offset_line_enable != 0;
state.polygon_offset.point_enable = regs.polygon_offset_point_enable != 0;
@@ -1257,14 +1116,17 @@ void RasterizerOpenGL::SyncPolygonOffset() {
state.polygon_offset.clamp = regs.polygon_offset_clamp;
}
-void RasterizerOpenGL::CheckAlphaTests() {
- const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
+void RasterizerOpenGL::SyncAlphaTest() {
+ const auto& regs = system.GPU().Maxwell3D().regs;
+ UNIMPLEMENTED_IF_MSG(regs.alpha_test_enabled != 0 && regs.rt_control.count > 1,
+ "Alpha Testing is enabled with more than one rendertarget");
- if (regs.alpha_test_enabled != 0 && regs.rt_control.count > 1) {
- LOG_CRITICAL(Render_OpenGL, "Alpha Testing is enabled with Multiple Render Targets, "
- "this behavior is undefined.");
- UNREACHABLE();
+ state.alpha_test.enabled = regs.alpha_test_enabled;
+ if (!state.alpha_test.enabled) {
+ return;
}
+ state.alpha_test.func = MaxwellToGL::ComparisonOp(regs.alpha_test_func);
+ state.alpha_test.ref = regs.alpha_test_ref;
}
} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index 2f0524f85..f7671ff5d 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -12,27 +12,26 @@
#include <optional>
#include <tuple>
#include <utility>
-#include <vector>
#include <boost/icl/interval_map.hpp>
-#include <boost/range/iterator_range.hpp>
#include <glad/glad.h>
#include "common/common_types.h"
+#include "video_core/engines/const_buffer_info.h"
#include "video_core/engines/maxwell_3d.h"
-#include "video_core/memory_manager.h"
#include "video_core/rasterizer_cache.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_global_cache.h"
-#include "video_core/renderer_opengl/gl_primitive_assembler.h"
#include "video_core/renderer_opengl/gl_rasterizer_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_gen.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.h"
-#include "video_core/renderer_opengl/gl_stream_buffer.h"
+#include "video_core/renderer_opengl/utils.h"
namespace Core {
class System;
@@ -50,16 +49,16 @@ struct FramebufferCacheKey;
class RasterizerOpenGL : public VideoCore::RasterizerInterface {
public:
- explicit RasterizerOpenGL(Core::Frontend::EmuWindow& window, Core::System& system,
+ explicit RasterizerOpenGL(Core::System& system, Core::Frontend::EmuWindow& emu_window,
ScreenInfo& info);
~RasterizerOpenGL() override;
void DrawArrays() override;
void Clear() override;
void FlushAll() override;
- void FlushRegion(VAddr addr, u64 size) override;
- void InvalidateRegion(VAddr addr, u64 size) override;
- void FlushAndInvalidateRegion(VAddr addr, u64 size) override;
+ void FlushRegion(CacheAddr addr, u64 size) override;
+ void InvalidateRegion(CacheAddr addr, u64 size) override;
+ void FlushAndInvalidateRegion(CacheAddr addr, u64 size) override;
bool AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Regs::Surface& src,
const Tegra::Engines::Fermi2D::Regs::Surface& dst,
const Common::Rectangle<u32>& src_rect,
@@ -67,7 +66,7 @@ public:
bool AccelerateDisplay(const Tegra::FramebufferConfig& config, VAddr framebuffer_addr,
u32 pixel_stride) override;
bool AccelerateDrawBatch(bool is_indexed) override;
- void UpdatePagesCachedCount(Tegra::GPUVAddr addr, u64 size, int delta) override;
+ void UpdatePagesCachedCount(VAddr addr, u64 size, int delta) override;
void LoadDiskResources(const std::atomic_bool& stop_loading,
const VideoCore::DiskResourceLoadCallback& callback) override;
@@ -76,38 +75,7 @@ public:
static_assert(MaxConstbufferSize % sizeof(GLvec4) == 0,
"The maximum size of a constbuffer must be a multiple of the size of GLvec4");
- static constexpr std::size_t MaxGlobalMemorySize = 0x10000;
- static_assert(MaxGlobalMemorySize % sizeof(float) == 0,
- "The maximum size of a global memory must be a multiple of the size of float");
-
private:
- class SamplerInfo {
- public:
- OGLSampler sampler;
-
- /// Creates the sampler object, initializing its state so that it's in sync with the
- /// SamplerInfo struct.
- void Create();
- /// Syncs the sampler object with the config, updating any necessary state.
- void SyncWithConfig(const Tegra::Texture::TSCEntry& info);
-
- private:
- Tegra::Texture::TextureFilter mag_filter = Tegra::Texture::TextureFilter::Nearest;
- Tegra::Texture::TextureFilter min_filter = Tegra::Texture::TextureFilter::Nearest;
- Tegra::Texture::TextureMipmapFilter mip_filter = Tegra::Texture::TextureMipmapFilter::None;
- Tegra::Texture::WrapMode wrap_u = Tegra::Texture::WrapMode::ClampToEdge;
- Tegra::Texture::WrapMode wrap_v = Tegra::Texture::WrapMode::ClampToEdge;
- Tegra::Texture::WrapMode wrap_p = Tegra::Texture::WrapMode::ClampToEdge;
- bool uses_depth_compare = false;
- Tegra::Texture::DepthCompareFunc depth_compare_func =
- Tegra::Texture::DepthCompareFunc::Always;
- GLvec4 border_color = {};
- float min_lod = 0.0f;
- float max_lod = 16.0f;
- float lod_bias = 0.0f;
- float max_anisotropic = 1.0f;
- };
-
struct FramebufferConfigState {
bool using_color_fb{};
bool using_depth_fb{};
@@ -139,17 +107,20 @@ private:
bool preserve_contents = true, std::optional<std::size_t> single_color_target = {});
/// Configures the current constbuffers to use for the draw command.
- void SetupConstBuffers(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, const Shader& shader,
- GLuint program_handle, BaseBindings base_bindings);
+ void SetupDrawConstBuffers(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage,
+ const Shader& shader);
+
+ /// Configures a constant buffer.
+ void SetupConstBuffer(const Tegra::Engines::ConstBufferInfo& buffer,
+ const GLShader::ConstBufferEntry& entry);
/// Configures the current global memory entries to use for the draw command.
void SetupGlobalRegions(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage,
- const Shader& shader, GLenum primitive_mode,
- BaseBindings base_bindings);
+ const Shader& shader);
/// Configures the current textures to use for the draw command.
void SetupTextures(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, const Shader& shader,
- GLuint program_handle, BaseBindings base_bindings);
+ BaseBindings base_bindings);
/// Syncs the viewport and depth range to match the guest state
void SyncViewport(OpenGLState& current_state);
@@ -200,21 +171,22 @@ private:
/// Syncs the polygon offsets
void SyncPolygonOffset();
- /// Check asserts for alpha testing.
- void CheckAlphaTests();
+ /// Syncs the alpha test state to match the guest state
+ void SyncAlphaTest();
/// Check for extension that are not strictly required
/// but are needed for correct emulation
void CheckExtensions();
+ const Device device;
OpenGLState state;
RasterizerCacheOpenGL res_cache;
ShaderCacheOpenGL shader_cache;
GlobalRegionCacheOpenGL global_cache;
+ SamplerCacheOpenGL sampler_cache;
- Core::Frontend::EmuWindow& emu_window;
-
+ Core::System& system;
ScreenInfo& screen_info;
std::unique_ptr<GLShader::ProgramManager> shader_program_manager;
@@ -227,12 +199,11 @@ private:
FramebufferConfigState current_framebuffer_config_state;
std::pair<bool, bool> current_depth_stencil_usage{};
- std::array<SamplerInfo, Tegra::Engines::Maxwell3D::Regs::NumTextureSamplers> texture_samplers;
-
static constexpr std::size_t STREAM_BUFFER_SIZE = 128 * 1024 * 1024;
OGLBufferCache buffer_cache;
- PrimitiveAssembler primitive_assembler{buffer_cache};
- GLint uniform_buffer_alignment;
+
+ BindBuffersRangePushBuffer bind_ubo_pushbuffer{GL_UNIFORM_BUFFER};
+ BindBuffersRangePushBuffer bind_ssbo_pushbuffer{GL_SHADER_STORAGE_BUFFER};
std::size_t CalculateVertexArraysSize() const;
diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
index 876698b37..a7681902e 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
@@ -13,9 +13,9 @@
#include "common/scope_exit.h"
#include "core/core.h"
#include "core/hle/kernel/process.h"
-#include "core/memory.h"
#include "core/settings.h"
#include "video_core/engines/maxwell_3d.h"
+#include "video_core/memory_manager.h"
#include "video_core/morton.h"
#include "video_core/renderer_opengl/gl_rasterizer.h"
#include "video_core/renderer_opengl/gl_rasterizer_cache.h"
@@ -55,12 +55,11 @@ static void ApplyTextureDefaults(GLuint texture, u32 max_mip_level) {
}
}
-void SurfaceParams::InitCacheParameters(Tegra::GPUVAddr gpu_addr_) {
+void SurfaceParams::InitCacheParameters(GPUVAddr gpu_addr_) {
auto& memory_manager{Core::System::GetInstance().GPU().MemoryManager()};
- const auto cpu_addr{memory_manager.GpuToCpuAddress(gpu_addr_)};
- addr = cpu_addr ? *cpu_addr : 0;
gpu_addr = gpu_addr_;
+ host_ptr = memory_manager.GetPointer(gpu_addr_);
size_in_bytes = SizeInBytesRaw();
if (IsPixelFormatASTC(pixel_format)) {
@@ -113,11 +112,26 @@ std::size_t SurfaceParams::InnerMemorySize(bool force_gl, bool layer_only,
params.pixel_format = PixelFormatFromTextureFormat(config.tic.format, config.tic.r_type.Value(),
params.srgb_conversion);
- if (params.pixel_format == PixelFormat::R16U && config.tsc.depth_compare_enabled) {
+ if (config.tsc.depth_compare_enabled) {
// Some titles create a 'R16U' (normalized 16-bit) texture with depth_compare enabled,
// then attempt to sample from it via a shadow sampler. Convert format to Z16 (which also
// causes GetFormatType to properly return 'Depth' below).
- params.pixel_format = PixelFormat::Z16;
+ if (GetFormatType(params.pixel_format) == SurfaceType::ColorTexture) {
+ switch (params.pixel_format) {
+ case PixelFormat::R16S:
+ case PixelFormat::R16U:
+ case PixelFormat::R16F:
+ params.pixel_format = PixelFormat::Z16;
+ break;
+ case PixelFormat::R32F:
+ params.pixel_format = PixelFormat::Z32F;
+ break;
+ default:
+ LOG_WARNING(HW_GPU, "Color texture format being used with depth compare: {}",
+ static_cast<u32>(params.pixel_format));
+ break;
+ }
+ }
}
params.component_type = ComponentTypeFromTexture(config.tic.r_type.Value());
@@ -223,7 +237,7 @@ std::size_t SurfaceParams::InnerMemorySize(bool force_gl, bool layer_only,
}
/*static*/ SurfaceParams SurfaceParams::CreateForDepthBuffer(
- u32 zeta_width, u32 zeta_height, Tegra::GPUVAddr zeta_address, Tegra::DepthFormat format,
+ u32 zeta_width, u32 zeta_height, GPUVAddr zeta_address, Tegra::DepthFormat format,
u32 block_width, u32 block_height, u32 block_depth,
Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout type) {
SurfaceParams params{};
@@ -267,6 +281,7 @@ std::size_t SurfaceParams::InnerMemorySize(bool force_gl, bool layer_only,
params.component_type = ComponentTypeFromRenderTarget(config.format);
params.type = GetFormatType(params.pixel_format);
params.width = config.width;
+ params.pitch = config.pitch;
params.height = config.height;
params.unaligned_height = config.height;
params.target = SurfaceTarget::Texture2D;
@@ -400,6 +415,27 @@ static const FormatTuple& GetFormatTuple(PixelFormat pixel_format, ComponentType
return format;
}
+/// Returns the discrepant array target
+constexpr GLenum GetArrayDiscrepantTarget(SurfaceTarget target) {
+ switch (target) {
+ case SurfaceTarget::Texture1D:
+ return GL_TEXTURE_1D_ARRAY;
+ case SurfaceTarget::Texture2D:
+ return GL_TEXTURE_2D_ARRAY;
+ case SurfaceTarget::Texture3D:
+ return GL_NONE;
+ case SurfaceTarget::Texture1DArray:
+ return GL_TEXTURE_1D;
+ case SurfaceTarget::Texture2DArray:
+ return GL_TEXTURE_2D;
+ case SurfaceTarget::TextureCubemap:
+ return GL_TEXTURE_CUBE_MAP_ARRAY;
+ case SurfaceTarget::TextureCubeArray:
+ return GL_TEXTURE_CUBE_MAP;
+ }
+ return GL_NONE;
+}
+
Common::Rectangle<u32> SurfaceParams::GetRect(u32 mip_level) const {
u32 actual_height{std::max(1U, unaligned_height >> mip_level)};
if (IsPixelFormatASTC(pixel_format)) {
@@ -425,7 +461,7 @@ void SwizzleFunc(const MortonSwizzleMode& mode, const SurfaceParams& params,
MortonSwizzle(mode, params.pixel_format, params.MipWidth(mip_level),
params.MipBlockHeight(mip_level), params.MipHeight(mip_level),
params.MipBlockDepth(mip_level), 1, params.tile_width_spacing,
- gl_buffer.data() + offset_gl, gl_size, params.addr + offset);
+ gl_buffer.data() + offset_gl, params.host_ptr + offset);
offset += layer_size;
offset_gl += gl_size;
}
@@ -434,7 +470,7 @@ void SwizzleFunc(const MortonSwizzleMode& mode, const SurfaceParams& params,
MortonSwizzle(mode, params.pixel_format, params.MipWidth(mip_level),
params.MipBlockHeight(mip_level), params.MipHeight(mip_level),
params.MipBlockDepth(mip_level), depth, params.tile_width_spacing,
- gl_buffer.data(), gl_buffer.size(), params.addr + offset);
+ gl_buffer.data(), params.host_ptr + offset);
}
}
@@ -492,9 +528,9 @@ void RasterizerCacheOpenGL::CopySurface(const Surface& src_surface, const Surfac
"reinterpretation but the texture is tiled.");
}
const std::size_t remaining_size = dst_params.size_in_bytes - src_params.size_in_bytes;
-
+ auto& memory_manager{Core::System::GetInstance().GPU().MemoryManager()};
glBufferSubData(GL_PIXEL_PACK_BUFFER, src_params.size_in_bytes, remaining_size,
- Memory::GetPointer(dst_params.addr + src_params.size_in_bytes));
+ memory_manager.GetPointer(dst_params.gpu_addr + src_params.size_in_bytes));
}
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
@@ -542,8 +578,14 @@ void RasterizerCacheOpenGL::CopySurface(const Surface& src_surface, const Surfac
}
CachedSurface::CachedSurface(const SurfaceParams& params)
- : params(params), gl_target(SurfaceTargetToGL(params.target)),
- cached_size_in_bytes(params.size_in_bytes) {
+ : RasterizerCacheObject{params.host_ptr}, params{params},
+ gl_target{SurfaceTargetToGL(params.target)}, cached_size_in_bytes{params.size_in_bytes} {
+
+ const auto optional_cpu_addr{
+ Core::System::GetInstance().GPU().MemoryManager().GpuToCpuAddress(params.gpu_addr)};
+ ASSERT_MSG(optional_cpu_addr, "optional_cpu_addr is invalid");
+ cpu_addr = *optional_cpu_addr;
+
texture.Create(gl_target);
// TODO(Rodrigo): Using params.GetRect() returns a different size than using its Mip*(0)
@@ -582,25 +624,15 @@ CachedSurface::CachedSurface(const SurfaceParams& params)
ApplyTextureDefaults(texture.handle, params.max_mip_level);
- OpenGL::LabelGLObject(GL_TEXTURE, texture.handle, params.addr, params.IdentityString());
-
- // Clamp size to mapped GPU memory region
- // TODO(bunnei): Super Mario Odyssey maps a 0x40000 byte region and then uses it for a 0x80000
- // R32F render buffer. We do not yet know if this is a game bug or something else, but this
- // check is necessary to prevent flushing from overwriting unmapped memory.
-
- auto& memory_manager{Core::System::GetInstance().GPU().MemoryManager()};
- const u64 max_size{memory_manager.GetRegionEnd(params.gpu_addr) - params.gpu_addr};
- if (cached_size_in_bytes > max_size) {
- LOG_ERROR(HW_GPU, "Surface size {} exceeds region size {}", params.size_in_bytes, max_size);
- cached_size_in_bytes = max_size;
- }
+ OpenGL::LabelGLObject(GL_TEXTURE, texture.handle, params.gpu_addr, params.IdentityString());
}
MICROPROFILE_DEFINE(OpenGL_SurfaceLoad, "OpenGL", "Surface Load", MP_RGB(128, 192, 64));
-void CachedSurface::LoadGLBuffer() {
+void CachedSurface::LoadGLBuffer(RasterizerTemporaryMemory& res_cache_tmp_mem) {
MICROPROFILE_SCOPE(OpenGL_SurfaceLoad);
- gl_buffer.resize(params.max_mip_level);
+ auto& gl_buffer = res_cache_tmp_mem.gl_buffer;
+ if (gl_buffer.size() < params.max_mip_level)
+ gl_buffer.resize(params.max_mip_level);
for (u32 i = 0; i < params.max_mip_level; i++)
gl_buffer[i].resize(params.GetMipmapSizeGL(i));
if (params.is_tiled) {
@@ -610,14 +642,16 @@ void CachedSurface::LoadGLBuffer() {
SwizzleFunc(MortonSwizzleMode::MortonToLinear, params, gl_buffer[i], i);
} else {
const u32 bpp = params.GetFormatBpp() / 8;
- const u32 copy_size = params.width * bpp;
+ const u32 copy_size = (params.width * bpp + GetDefaultBlockWidth(params.pixel_format) - 1) /
+ GetDefaultBlockWidth(params.pixel_format);
if (params.pitch == copy_size) {
- std::memcpy(gl_buffer[0].data(), Memory::GetPointer(params.addr),
- params.size_in_bytes_gl);
+ std::memcpy(gl_buffer[0].data(), params.host_ptr, params.size_in_bytes_gl);
} else {
- const u8* start = Memory::GetPointer(params.addr);
+ const u32 height = (params.height + GetDefaultBlockHeight(params.pixel_format) - 1) /
+ GetDefaultBlockHeight(params.pixel_format);
+ const u8* start{params.host_ptr};
u8* write_to = gl_buffer[0].data();
- for (u32 h = params.height; h > 0; h--) {
+ for (u32 h = height; h > 0; h--) {
std::memcpy(write_to, start, copy_size);
start += params.pitch;
write_to += copy_size;
@@ -639,18 +673,18 @@ void CachedSurface::LoadGLBuffer() {
}
MICROPROFILE_DEFINE(OpenGL_SurfaceFlush, "OpenGL", "Surface Flush", MP_RGB(128, 192, 64));
-void CachedSurface::FlushGLBuffer() {
+void CachedSurface::FlushGLBuffer(RasterizerTemporaryMemory& res_cache_tmp_mem) {
MICROPROFILE_SCOPE(OpenGL_SurfaceFlush);
ASSERT_MSG(!IsPixelFormatASTC(params.pixel_format), "Unimplemented");
+ auto& gl_buffer = res_cache_tmp_mem.gl_buffer;
// OpenGL temporary buffer needs to be big enough to store raw texture size
- gl_buffer.resize(1);
gl_buffer[0].resize(GetSizeInBytes());
const FormatTuple& tuple = GetFormatTuple(params.pixel_format, params.component_type);
- // Ensure no bad interactions with GL_UNPACK_ALIGNMENT
- ASSERT(params.width * GetBytesPerPixel(params.pixel_format) % 4 == 0);
+ const u32 align = std::clamp(params.RowAlign(0), 1U, 8U);
+ glPixelStorei(GL_PACK_ALIGNMENT, align);
glPixelStorei(GL_PACK_ROW_LENGTH, static_cast<GLint>(params.width));
ASSERT(!tuple.compressed);
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
@@ -659,8 +693,6 @@ void CachedSurface::FlushGLBuffer() {
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
Tegra::Texture::ConvertFromHostToGuest(gl_buffer[0].data(), params.pixel_format, params.width,
params.height, params.depth, true, true);
- const u8* const texture_src_data = Memory::GetPointer(params.addr);
- ASSERT(texture_src_data);
if (params.is_tiled) {
ASSERT_MSG(params.block_width == 1, "Block width is defined as {} on texture type {}",
params.block_width, static_cast<u32>(params.target));
@@ -670,9 +702,9 @@ void CachedSurface::FlushGLBuffer() {
const u32 bpp = params.GetFormatBpp() / 8;
const u32 copy_size = params.width * bpp;
if (params.pitch == copy_size) {
- std::memcpy(Memory::GetPointer(params.addr), gl_buffer[0].data(), GetSizeInBytes());
+ std::memcpy(params.host_ptr, gl_buffer[0].data(), GetSizeInBytes());
} else {
- u8* start = Memory::GetPointer(params.addr);
+ u8* start{params.host_ptr};
const u8* read_to = gl_buffer[0].data();
for (u32 h = params.height; h > 0; h--) {
std::memcpy(start, read_to, copy_size);
@@ -683,10 +715,12 @@ void CachedSurface::FlushGLBuffer() {
}
}
-void CachedSurface::UploadGLMipmapTexture(u32 mip_map, GLuint read_fb_handle,
- GLuint draw_fb_handle) {
+void CachedSurface::UploadGLMipmapTexture(RasterizerTemporaryMemory& res_cache_tmp_mem, u32 mip_map,
+ GLuint read_fb_handle, GLuint draw_fb_handle) {
const auto& rect{params.GetRect(mip_map)};
+ auto& gl_buffer = res_cache_tmp_mem.gl_buffer;
+
// Load data from memory to the surface
const auto x0 = static_cast<GLint>(rect.left);
const auto y0 = static_cast<GLint>(rect.bottom);
@@ -697,8 +731,8 @@ void CachedSurface::UploadGLMipmapTexture(u32 mip_map, GLuint read_fb_handle,
const FormatTuple& tuple = GetFormatTuple(params.pixel_format, params.component_type);
- // Ensure no bad interactions with GL_UNPACK_ALIGNMENT
- ASSERT(params.MipWidth(mip_map) * GetBytesPerPixel(params.pixel_format) % 4 == 0);
+ const u32 align = std::clamp(params.RowAlign(mip_map), 1U, 8U);
+ glPixelStorei(GL_UNPACK_ALIGNMENT, align);
glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(params.MipWidth(mip_map)));
const auto image_size = static_cast<GLsizei>(params.GetMipmapSizeGL(mip_map, false));
@@ -771,7 +805,6 @@ void CachedSurface::UploadGLMipmapTexture(u32 mip_map, GLuint read_fb_handle,
tuple.type, &gl_buffer[mip_map][buffer_offset]);
break;
case SurfaceTarget::TextureCubemap: {
- std::size_t start = buffer_offset;
for (std::size_t face = 0; face < params.depth; ++face) {
glTextureSubImage3D(texture.handle, mip_map, x0, y0, static_cast<GLint>(face),
static_cast<GLsizei>(rect.GetWidth()),
@@ -795,29 +828,32 @@ void CachedSurface::UploadGLMipmapTexture(u32 mip_map, GLuint read_fb_handle,
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
}
-void CachedSurface::EnsureTextureView() {
- if (texture_view.handle != 0)
+void CachedSurface::EnsureTextureDiscrepantView() {
+ if (discrepant_view.handle != 0)
return;
- const GLenum target{TargetLayer()};
+ const GLenum target{GetArrayDiscrepantTarget(params.target)};
+ ASSERT(target != GL_NONE);
+
const GLuint num_layers{target == GL_TEXTURE_CUBE_MAP_ARRAY ? 6u : 1u};
constexpr GLuint min_layer = 0;
constexpr GLuint min_level = 0;
- glGenTextures(1, &texture_view.handle);
- glTextureView(texture_view.handle, target, texture.handle, gl_internal_format, min_level,
+ glGenTextures(1, &discrepant_view.handle);
+ glTextureView(discrepant_view.handle, target, texture.handle, gl_internal_format, min_level,
params.max_mip_level, min_layer, num_layers);
- ApplyTextureDefaults(texture_view.handle, params.max_mip_level);
- glTextureParameteriv(texture_view.handle, GL_TEXTURE_SWIZZLE_RGBA,
+ ApplyTextureDefaults(discrepant_view.handle, params.max_mip_level);
+ glTextureParameteriv(discrepant_view.handle, GL_TEXTURE_SWIZZLE_RGBA,
reinterpret_cast<const GLint*>(swizzle.data()));
}
MICROPROFILE_DEFINE(OpenGL_TextureUL, "OpenGL", "Texture Upload", MP_RGB(128, 192, 64));
-void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle) {
+void CachedSurface::UploadGLTexture(RasterizerTemporaryMemory& res_cache_tmp_mem,
+ GLuint read_fb_handle, GLuint draw_fb_handle) {
MICROPROFILE_SCOPE(OpenGL_TextureUL);
for (u32 i = 0; i < params.max_mip_level; i++)
- UploadGLMipmapTexture(i, read_fb_handle, draw_fb_handle);
+ UploadGLMipmapTexture(res_cache_tmp_mem, i, read_fb_handle, draw_fb_handle);
}
void CachedSurface::UpdateSwizzle(Tegra::Texture::SwizzleSource swizzle_x,
@@ -834,8 +870,8 @@ void CachedSurface::UpdateSwizzle(Tegra::Texture::SwizzleSource swizzle_x,
swizzle = {new_x, new_y, new_z, new_w};
const auto swizzle_data = reinterpret_cast<const GLint*>(swizzle.data());
glTextureParameteriv(texture.handle, GL_TEXTURE_SWIZZLE_RGBA, swizzle_data);
- if (texture_view.handle != 0) {
- glTextureParameteriv(texture_view.handle, GL_TEXTURE_SWIZZLE_RGBA, swizzle_data);
+ if (discrepant_view.handle != 0) {
+ glTextureParameteriv(discrepant_view.handle, GL_TEXTURE_SWIZZLE_RGBA, swizzle_data);
}
}
@@ -897,19 +933,19 @@ Surface RasterizerCacheOpenGL::GetColorBufferSurface(std::size_t index, bool pre
}
void RasterizerCacheOpenGL::LoadSurface(const Surface& surface) {
- surface->LoadGLBuffer();
- surface->UploadGLTexture(read_framebuffer.handle, draw_framebuffer.handle);
+ surface->LoadGLBuffer(temporal_memory);
+ surface->UploadGLTexture(temporal_memory, read_framebuffer.handle, draw_framebuffer.handle);
surface->MarkAsModified(false, *this);
surface->MarkForReload(false);
}
Surface RasterizerCacheOpenGL::GetSurface(const SurfaceParams& params, bool preserve_contents) {
- if (params.addr == 0 || params.height * params.width == 0) {
+ if (!params.IsValid()) {
return {};
}
// Look up surface in the cache based on address
- Surface surface{TryGet(params.addr)};
+ Surface surface{TryGet(params.host_ptr)};
if (surface) {
if (surface->GetSurfaceParams().IsCompatibleSurface(params)) {
// Use the cached surface as-is unless it's not synced with memory
@@ -920,7 +956,7 @@ Surface RasterizerCacheOpenGL::GetSurface(const SurfaceParams& params, bool pres
// If surface parameters changed and we care about keeping the previous data, recreate
// the surface from the old one
Surface new_surface{RecreateSurface(surface, params)};
- UnregisterSurface(surface);
+ Unregister(surface);
Register(new_surface);
if (new_surface->IsUploaded()) {
RegisterReinterpretSurface(new_surface);
@@ -928,7 +964,7 @@ Surface RasterizerCacheOpenGL::GetSurface(const SurfaceParams& params, bool pres
return new_surface;
} else {
// Delete the old surface before creating a new one to prevent collisions.
- UnregisterSurface(surface);
+ Unregister(surface);
}
}
@@ -958,14 +994,16 @@ void RasterizerCacheOpenGL::FastLayeredCopySurface(const Surface& src_surface,
const Surface& dst_surface) {
const auto& init_params{src_surface->GetSurfaceParams()};
const auto& dst_params{dst_surface->GetSurfaceParams()};
- VAddr address = init_params.addr;
- const std::size_t layer_size = dst_params.LayerMemorySize();
+ auto& memory_manager{Core::System::GetInstance().GPU().MemoryManager()};
+ GPUVAddr address{init_params.gpu_addr};
+ const std::size_t layer_size{dst_params.LayerMemorySize()};
for (u32 layer = 0; layer < dst_params.depth; layer++) {
for (u32 mipmap = 0; mipmap < dst_params.max_mip_level; mipmap++) {
- const VAddr sub_address = address + dst_params.GetMipmapLevelOffset(mipmap);
- const Surface& copy = TryGet(sub_address);
- if (!copy)
+ const GPUVAddr sub_address{address + dst_params.GetMipmapLevelOffset(mipmap)};
+ const Surface& copy{TryGet(memory_manager.GetPointer(sub_address))};
+ if (!copy) {
continue;
+ }
const auto& src_params{copy->GetSurfaceParams()};
const u32 width{std::min(src_params.width, dst_params.MipWidth(mipmap))};
const u32 height{std::min(src_params.height, dst_params.MipHeight(mipmap))};
@@ -1140,7 +1178,8 @@ void RasterizerCacheOpenGL::AccurateCopySurface(const Surface& src_surface,
const auto& dst_params{dst_surface->GetSurfaceParams()};
// Flush enough memory for both the source and destination surface
- FlushRegion(src_params.addr, std::max(src_params.MemorySize(), dst_params.MemorySize()));
+ FlushRegion(ToCacheAddr(src_params.host_ptr),
+ std::max(src_params.MemorySize(), dst_params.MemorySize()));
LoadSurface(dst_surface);
}
@@ -1159,10 +1198,16 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface,
return new_surface;
}
+ const bool old_compressed =
+ GetFormatTuple(old_params.pixel_format, old_params.component_type).compressed;
+ const bool new_compressed =
+ GetFormatTuple(new_params.pixel_format, new_params.component_type).compressed;
+ const bool compatible_formats =
+ GetFormatBpp(old_params.pixel_format) == GetFormatBpp(new_params.pixel_format) &&
+ !(old_compressed || new_compressed);
// For compatible surfaces, we can just do fast glCopyImageSubData based copy
- if (old_params.target == new_params.target && old_params.type == new_params.type &&
- old_params.depth == new_params.depth && old_params.depth == 1 &&
- GetFormatBpp(old_params.pixel_format) == GetFormatBpp(new_params.pixel_format)) {
+ if (old_params.target == new_params.target && old_params.depth == new_params.depth &&
+ old_params.depth == 1 && compatible_formats) {
FastCopySurface(old_surface, new_surface);
return new_surface;
}
@@ -1177,7 +1222,7 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface,
case SurfaceTarget::TextureCubemap:
case SurfaceTarget::Texture2DArray:
case SurfaceTarget::TextureCubeArray:
- if (old_params.pixel_format == new_params.pixel_format)
+ if (compatible_formats)
FastLayeredCopySurface(old_surface, new_surface);
else {
AccurateCopySurface(old_surface, new_surface);
@@ -1192,8 +1237,8 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface,
return new_surface;
}
-Surface RasterizerCacheOpenGL::TryFindFramebufferSurface(VAddr addr) const {
- return TryGet(addr);
+Surface RasterizerCacheOpenGL::TryFindFramebufferSurface(const u8* host_ptr) const {
+ return TryGet(host_ptr);
}
void RasterizerCacheOpenGL::ReserveSurface(const Surface& surface) {
@@ -1220,9 +1265,9 @@ static std::optional<u32> TryFindBestMipMap(std::size_t memory, const SurfacePar
return {};
}
-static std::optional<u32> TryFindBestLayer(VAddr addr, const SurfaceParams params, u32 mipmap) {
- const std::size_t size = params.LayerMemorySize();
- VAddr start = params.addr + params.GetMipmapLevelOffset(mipmap);
+static std::optional<u32> TryFindBestLayer(GPUVAddr addr, const SurfaceParams params, u32 mipmap) {
+ const std::size_t size{params.LayerMemorySize()};
+ GPUVAddr start{params.gpu_addr + params.GetMipmapLevelOffset(mipmap)};
for (u32 i = 0; i < params.depth; i++) {
if (start == addr) {
return {i};
@@ -1244,7 +1289,7 @@ static bool LayerFitReinterpretSurface(RasterizerCacheOpenGL& cache, const Surfa
src_params.height == dst_params.MipHeight(*level) &&
src_params.block_height >= dst_params.MipBlockHeight(*level)) {
const std::optional<u32> slot =
- TryFindBestLayer(render_surface->GetAddr(), dst_params, *level);
+ TryFindBestLayer(render_surface->GetSurfaceParams().gpu_addr, dst_params, *level);
if (slot.has_value()) {
glCopyImageSubData(render_surface->Texture().handle,
SurfaceTargetToGL(src_params.target), 0, 0, 0, 0,
@@ -1260,8 +1305,8 @@ static bool LayerFitReinterpretSurface(RasterizerCacheOpenGL& cache, const Surfa
}
static bool IsReinterpretInvalid(const Surface render_surface, const Surface blitted_surface) {
- const VAddr bound1 = blitted_surface->GetAddr() + blitted_surface->GetMemorySize();
- const VAddr bound2 = render_surface->GetAddr() + render_surface->GetMemorySize();
+ const VAddr bound1 = blitted_surface->GetCpuAddr() + blitted_surface->GetMemorySize();
+ const VAddr bound2 = render_surface->GetCpuAddr() + render_surface->GetMemorySize();
if (bound2 > bound1)
return true;
const auto& dst_params = blitted_surface->GetSurfaceParams();
@@ -1279,12 +1324,12 @@ static bool IsReinterpretInvalidSecond(const Surface render_surface,
bool RasterizerCacheOpenGL::PartialReinterpretSurface(Surface triggering_surface,
Surface intersect) {
if (IsReinterpretInvalid(triggering_surface, intersect)) {
- UnregisterSurface(intersect);
+ Unregister(intersect);
return false;
}
if (!LayerFitReinterpretSurface(*this, triggering_surface, intersect)) {
if (IsReinterpretInvalidSecond(triggering_surface, intersect)) {
- UnregisterSurface(intersect);
+ Unregister(intersect);
return false;
}
FlushObject(intersect);
@@ -1304,7 +1349,8 @@ void RasterizerCacheOpenGL::SignalPreDrawCall() {
void RasterizerCacheOpenGL::SignalPostDrawCall() {
for (u32 i = 0; i < Maxwell::NumRenderTargets; i++) {
if (current_color_buffers[i] != nullptr) {
- Surface intersect = CollideOnReinterpretedSurface(current_color_buffers[i]->GetAddr());
+ Surface intersect =
+ CollideOnReinterpretedSurface(current_color_buffers[i]->GetCacheAddr());
if (intersect != nullptr) {
PartialReinterpretSurface(current_color_buffers[i], intersect);
texception = true;
diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.h b/src/video_core/renderer_opengl/gl_rasterizer_cache.h
index 797bbdc9c..6263ef3e7 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer_cache.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.h
@@ -5,13 +5,13 @@
#pragma once
#include <array>
-#include <map>
#include <memory>
#include <string>
-#include <unordered_set>
+#include <tuple>
#include <vector>
#include "common/alignment.h"
+#include "common/bit_util.h"
#include "common/common_types.h"
#include "common/hash.h"
#include "common/math_util.h"
@@ -109,6 +109,11 @@ struct SurfaceParams {
return size;
}
+ /// Returns true if the parameters constitute a valid rasterizer surface.
+ bool IsValid() const {
+ return gpu_addr && host_ptr && height && width;
+ }
+
/// Returns the exact size of the memory occupied by a layer in a texture in VRAM, including
/// mipmaps.
std::size_t LayerMemorySize() const {
@@ -201,6 +206,13 @@ struct SurfaceParams {
return bd;
}
+ u32 RowAlign(u32 mip_level) const {
+ const u32 m_width = MipWidth(mip_level);
+ const u32 bytes_per_pixel = GetBytesPerPixel(pixel_format);
+ const u32 l2 = Common::CountTrailingZeroes32(m_width * bytes_per_pixel);
+ return (1U << l2);
+ }
+
/// Creates SurfaceParams from a texture configuration
static SurfaceParams CreateForTexture(const Tegra::Texture::FullTextureInfo& config,
const GLShader::SamplerEntry& entry);
@@ -210,7 +222,7 @@ struct SurfaceParams {
/// Creates SurfaceParams for a depth buffer configuration
static SurfaceParams CreateForDepthBuffer(
- u32 zeta_width, u32 zeta_height, Tegra::GPUVAddr zeta_address, Tegra::DepthFormat format,
+ u32 zeta_width, u32 zeta_height, GPUVAddr zeta_address, Tegra::DepthFormat format,
u32 block_width, u32 block_height, u32 block_depth,
Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout type);
@@ -232,7 +244,7 @@ struct SurfaceParams {
}
/// Initializes parameters for caching, should be called after everything has been initialized
- void InitCacheParameters(Tegra::GPUVAddr gpu_addr);
+ void InitCacheParameters(GPUVAddr gpu_addr);
std::string TargetName() const {
switch (target) {
@@ -296,8 +308,8 @@ struct SurfaceParams {
bool is_array;
bool srgb_conversion;
// Parameters used for caching
- VAddr addr;
- Tegra::GPUVAddr gpu_addr;
+ u8* host_ptr;
+ GPUVAddr gpu_addr;
std::size_t size_in_bytes;
std::size_t size_in_bytes_gl;
@@ -343,12 +355,18 @@ namespace OpenGL {
class RasterizerOpenGL;
+// This is used to store temporary big buffers,
+// instead of creating/destroying all the time
+struct RasterizerTemporaryMemory {
+ std::vector<std::vector<u8>> gl_buffer;
+};
+
class CachedSurface final : public RasterizerCacheObject {
public:
- CachedSurface(const SurfaceParams& params);
+ explicit CachedSurface(const SurfaceParams& params);
- VAddr GetAddr() const override {
- return params.addr;
+ VAddr GetCpuAddr() const override {
+ return cpu_addr;
}
std::size_t GetSizeInBytes() const override {
@@ -359,49 +377,34 @@ public:
return memory_size;
}
- void Flush() override {
- FlushGLBuffer();
- }
-
const OGLTexture& Texture() const {
return texture;
}
- const OGLTexture& TextureLayer() {
- if (params.is_array) {
- return Texture();
+ const OGLTexture& Texture(bool as_array) {
+ if (params.is_array == as_array) {
+ return texture;
+ } else {
+ EnsureTextureDiscrepantView();
+ return discrepant_view;
}
- EnsureTextureView();
- return texture_view;
}
GLenum Target() const {
return gl_target;
}
- GLenum TargetLayer() const {
- using VideoCore::Surface::SurfaceTarget;
- switch (params.target) {
- case SurfaceTarget::Texture1D:
- return GL_TEXTURE_1D_ARRAY;
- case SurfaceTarget::Texture2D:
- return GL_TEXTURE_2D_ARRAY;
- case SurfaceTarget::TextureCubemap:
- return GL_TEXTURE_CUBE_MAP_ARRAY;
- }
- return Target();
- }
-
const SurfaceParams& GetSurfaceParams() const {
return params;
}
// Read/Write data in Switch memory to/from gl_buffer
- void LoadGLBuffer();
- void FlushGLBuffer();
+ void LoadGLBuffer(RasterizerTemporaryMemory& res_cache_tmp_mem);
+ void FlushGLBuffer(RasterizerTemporaryMemory& res_cache_tmp_mem);
// Upload data in gl_buffer to this surface's texture
- void UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle);
+ void UploadGLTexture(RasterizerTemporaryMemory& res_cache_tmp_mem, GLuint read_fb_handle,
+ GLuint draw_fb_handle);
void UpdateSwizzle(Tegra::Texture::SwizzleSource swizzle_x,
Tegra::Texture::SwizzleSource swizzle_y,
@@ -429,13 +432,13 @@ public:
}
private:
- void UploadGLMipmapTexture(u32 mip_map, GLuint read_fb_handle, GLuint draw_fb_handle);
+ void UploadGLMipmapTexture(RasterizerTemporaryMemory& res_cache_tmp_mem, u32 mip_map,
+ GLuint read_fb_handle, GLuint draw_fb_handle);
- void EnsureTextureView();
+ void EnsureTextureDiscrepantView();
OGLTexture texture;
- OGLTexture texture_view;
- std::vector<std::vector<u8>> gl_buffer;
+ OGLTexture discrepant_view;
SurfaceParams params{};
GLenum gl_target{};
GLenum gl_internal_format{};
@@ -444,6 +447,7 @@ private:
std::size_t memory_size;
bool reinterpreted = false;
bool must_reload = false;
+ VAddr cpu_addr{};
};
class RasterizerCacheOpenGL final : public RasterizerCache<Surface> {
@@ -461,7 +465,7 @@ public:
Surface GetColorBufferSurface(std::size_t index, bool preserve_contents);
/// Tries to find a framebuffer using on the provided CPU address
- Surface TryFindFramebufferSurface(VAddr addr) const;
+ Surface TryFindFramebufferSurface(const u8* host_ptr) const;
/// Copies the contents of one surface to another
void FermiCopySurface(const Tegra::Engines::Fermi2D::Regs::Surface& src_config,
@@ -472,6 +476,11 @@ public:
void SignalPreDrawCall();
void SignalPostDrawCall();
+protected:
+ void FlushObjectInner(const Surface& object) override {
+ object->FlushGLBuffer(temporal_memory);
+ }
+
private:
void LoadSurface(const Surface& surface);
Surface GetSurface(const SurfaceParams& params, bool preserve_contents = true);
@@ -518,12 +527,14 @@ private:
std::array<Surface, Maxwell::NumRenderTargets> current_color_buffers;
Surface last_depth_buffer;
- using SurfaceIntervalCache = boost::icl::interval_map<VAddr, Surface>;
+ RasterizerTemporaryMemory temporal_memory;
+
+ using SurfaceIntervalCache = boost::icl::interval_map<CacheAddr, Surface>;
using SurfaceInterval = typename SurfaceIntervalCache::interval_type;
static auto GetReinterpretInterval(const Surface& object) {
- return SurfaceInterval::right_open(object->GetAddr() + 1,
- object->GetAddr() + object->GetMemorySize() - 1);
+ return SurfaceInterval::right_open(object->GetCacheAddr() + 1,
+ object->GetCacheAddr() + object->GetMemorySize() - 1);
}
// Reinterpreted surfaces are very fragil as the game may keep rendering into them.
@@ -535,7 +546,7 @@ private:
reinterpret_surface->MarkReinterpreted();
}
- Surface CollideOnReinterpretedSurface(VAddr addr) const {
+ Surface CollideOnReinterpretedSurface(CacheAddr addr) const {
const SurfaceInterval interval{addr};
for (auto& pair :
boost::make_iterator_range(reinterpreted_surfaces.equal_range(interval))) {
@@ -544,13 +555,17 @@ private:
return nullptr;
}
+ void Register(const Surface& object) override {
+ RasterizerCache<Surface>::Register(object);
+ }
+
/// Unregisters an object from the cache
- void UnregisterSurface(const Surface& object) {
+ void Unregister(const Surface& object) override {
if (object->IsReinterpreted()) {
auto interval = GetReinterpretInterval(object);
reinterpreted_surfaces.erase(interval);
}
- Unregister(object);
+ RasterizerCache<Surface>::Unregister(object);
}
};
diff --git a/src/video_core/renderer_opengl/gl_sampler_cache.cpp b/src/video_core/renderer_opengl/gl_sampler_cache.cpp
new file mode 100644
index 000000000..3ded5ecea
--- /dev/null
+++ b/src/video_core/renderer_opengl/gl_sampler_cache.cpp
@@ -0,0 +1,52 @@
+// 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 if (tsc.GetMaxAnisotropy() != 1) {
+ 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
new file mode 100644
index 000000000..defbc2d81
--- /dev/null
+++ b/src/video_core/renderer_opengl/gl_sampler_cache.h
@@ -0,0 +1,25 @@
+// 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;
+
+ GLuint ToSamplerType(const OGLSampler& sampler) const;
+};
+
+} // 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 4883e4f62..ac8a9e6b7 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp
@@ -2,17 +2,20 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <mutex>
+#include <thread>
#include <boost/functional/hash.hpp>
#include "common/assert.h"
#include "common/hash.h"
+#include "common/scope_exit.h"
#include "core/core.h"
-#include "core/memory.h"
+#include "core/frontend/emu_window.h"
#include "video_core/engines/maxwell_3d.h"
+#include "video_core/memory_manager.h"
#include "video_core/renderer_opengl/gl_rasterizer.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_disk_cache.h"
-#include "video_core/renderer_opengl/gl_shader_manager.h"
#include "video_core/renderer_opengl/utils.h"
#include "video_core/shader/shader_ir.h"
@@ -32,19 +35,22 @@ struct UnspecializedShader {
namespace {
/// Gets the address for the specified shader stage program
-VAddr GetShaderAddress(Maxwell::ShaderProgram program) {
- const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
- const auto& shader_config = gpu.regs.shader_config[static_cast<std::size_t>(program)];
- const auto address = gpu.memory_manager.GpuToCpuAddress(gpu.regs.code_address.CodeAddress() +
- shader_config.offset);
- ASSERT_MSG(address, "Invalid GPU address");
- return *address;
+GPUVAddr GetShaderAddress(Core::System& system, Maxwell::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;
}
/// Gets the shader program code from memory for the specified address
-ProgramCode GetShaderCode(VAddr addr) {
+ProgramCode GetShaderCode(Tegra::MemoryManager& memory_manager, const GPUVAddr gpu_addr,
+ const u8* host_ptr) {
ProgramCode program_code(VideoCommon::Shader::MAX_PROGRAM_LENGTH);
- Memory::ReadBlock(addr, program_code.data(), program_code.size() * sizeof(u64));
+ ASSERT_OR_EXECUTE(host_ptr != nullptr, {
+ std::fill(program_code.begin(), program_code.end(), 0);
+ return program_code;
+ });
+ memory_manager.ReadBlockUnsafe(gpu_addr, program_code.data(),
+ program_code.size() * sizeof(u64));
return program_code;
}
@@ -134,8 +140,8 @@ u64 GetUniqueIdentifier(Maxwell::ShaderProgram program_type, const ProgramCode&
}
/// Creates an unspecialized program from code streams
-GLShader::ProgramResult CreateProgram(Maxwell::ShaderProgram program_type, ProgramCode program_code,
- ProgramCode program_code_b) {
+GLShader::ProgramResult CreateProgram(const Device& device, Maxwell::ShaderProgram program_type,
+ ProgramCode program_code, ProgramCode program_code_b) {
GLShader::ShaderSetup setup(program_code);
if (program_type == Maxwell::ShaderProgram::VertexA) {
// VertexB is always enabled, so when VertexA is enabled, we have two vertex shaders.
@@ -149,11 +155,11 @@ GLShader::ProgramResult CreateProgram(Maxwell::ShaderProgram program_type, Progr
switch (program_type) {
case Maxwell::ShaderProgram::VertexA:
case Maxwell::ShaderProgram::VertexB:
- return GLShader::GenerateVertexShader(setup);
+ return GLShader::GenerateVertexShader(device, setup);
case Maxwell::ShaderProgram::Geometry:
- return GLShader::GenerateGeometryShader(setup);
+ return GLShader::GenerateGeometryShader(device, setup);
case Maxwell::ShaderProgram::Fragment:
- return GLShader::GenerateFragmentShader(setup);
+ return GLShader::GenerateFragmentShader(device, setup);
default:
LOG_CRITICAL(HW_GPU, "Unimplemented program_type={}", static_cast<u32>(program_type));
UNREACHABLE();
@@ -164,7 +170,8 @@ GLShader::ProgramResult CreateProgram(Maxwell::ShaderProgram program_type, Progr
CachedProgram SpecializeShader(const std::string& code, const GLShader::ShaderEntries& entries,
Maxwell::ShaderProgram program_type, BaseBindings base_bindings,
GLenum primitive_mode, bool hint_retrievable = false) {
- std::string source = "#version 430 core\n";
+ std::string source = "#version 430 core\n"
+ "#extension GL_ARB_separate_shader_objects : enable\n\n";
source += fmt::format("#define EMULATION_UBO_BINDING {}\n", base_bindings.cbuf++);
for (const auto& cbuf : entries.const_buffers) {
@@ -212,21 +219,20 @@ std::set<GLenum> GetSupportedFormats() {
return supported_formats;
}
-} // namespace
+} // Anonymous namespace
-CachedShader::CachedShader(VAddr addr, u64 unique_identifier, Maxwell::ShaderProgram program_type,
- ShaderDiskCacheOpenGL& disk_cache,
+CachedShader::CachedShader(const Device& device, VAddr cpu_addr, u64 unique_identifier,
+ Maxwell::ShaderProgram program_type, ShaderDiskCacheOpenGL& disk_cache,
const PrecompiledPrograms& precompiled_programs,
- ProgramCode&& program_code, ProgramCode&& program_code_b)
- : addr{addr}, unique_identifier{unique_identifier}, program_type{program_type},
- disk_cache{disk_cache}, precompiled_programs{precompiled_programs} {
-
- const std::size_t code_size = CalculateProgramSize(program_code);
- const std::size_t code_size_b =
- program_code_b.empty() ? 0 : CalculateProgramSize(program_code_b);
-
- GLShader::ProgramResult program_result =
- CreateProgram(program_type, program_code, program_code_b);
+ ProgramCode&& program_code, ProgramCode&& program_code_b, u8* host_ptr)
+ : RasterizerCacheObject{host_ptr}, host_ptr{host_ptr}, cpu_addr{cpu_addr},
+ unique_identifier{unique_identifier}, program_type{program_type}, disk_cache{disk_cache},
+ precompiled_programs{precompiled_programs} {
+ const std::size_t code_size{CalculateProgramSize(program_code)};
+ const std::size_t code_size_b{program_code_b.empty() ? 0
+ : CalculateProgramSize(program_code_b)};
+ GLShader::ProgramResult program_result{
+ CreateProgram(device, program_type, program_code, program_code_b)};
if (program_result.first.empty()) {
// TODO(Rodrigo): Unimplemented shader stages hit here, avoid using these for now
return;
@@ -243,13 +249,13 @@ CachedShader::CachedShader(VAddr addr, u64 unique_identifier, Maxwell::ShaderPro
disk_cache.SaveRaw(raw);
}
-CachedShader::CachedShader(VAddr addr, u64 unique_identifier, Maxwell::ShaderProgram program_type,
- ShaderDiskCacheOpenGL& disk_cache,
+CachedShader::CachedShader(VAddr cpu_addr, u64 unique_identifier,
+ Maxwell::ShaderProgram program_type, ShaderDiskCacheOpenGL& disk_cache,
const PrecompiledPrograms& precompiled_programs,
- GLShader::ProgramResult result)
- : addr{addr}, unique_identifier{unique_identifier}, program_type{program_type},
- disk_cache{disk_cache}, precompiled_programs{precompiled_programs} {
-
+ GLShader::ProgramResult result, u8* host_ptr)
+ : RasterizerCacheObject{host_ptr}, cpu_addr{cpu_addr}, unique_identifier{unique_identifier},
+ program_type{program_type}, disk_cache{disk_cache}, precompiled_programs{
+ precompiled_programs} {
code = std::move(result.first);
entries = result.second;
shader_length = entries.shader_length;
@@ -271,7 +277,7 @@ std::tuple<GLuint, BaseBindings> CachedShader::GetProgramHandle(GLenum primitive
disk_cache.SaveUsage(GetUsage(primitive_mode, base_bindings));
}
- LabelGLObject(GL_PROGRAM, program->handle, addr);
+ LabelGLObject(GL_PROGRAM, program->handle, cpu_addr);
}
handle = program->handle;
@@ -323,7 +329,7 @@ GLuint CachedShader::LazyGeometryProgram(CachedProgram& target_program, BaseBind
disk_cache.SaveUsage(GetUsage(primitive_mode, base_bindings));
}
- LabelGLObject(GL_PROGRAM, target_program->handle, addr, debug_name);
+ LabelGLObject(GL_PROGRAM, target_program->handle, cpu_addr, debug_name);
return target_program->handle;
};
@@ -342,8 +348,10 @@ ShaderDiskCacheUsage CachedShader::GetUsage(GLenum primitive_mode,
return {unique_identifier, base_bindings, primitive_mode};
}
-ShaderCacheOpenGL::ShaderCacheOpenGL(RasterizerOpenGL& rasterizer, Core::System& system)
- : RasterizerCache{rasterizer}, disk_cache{system} {}
+ShaderCacheOpenGL::ShaderCacheOpenGL(RasterizerOpenGL& rasterizer, Core::System& system,
+ Core::Frontend::EmuWindow& emu_window, const Device& device)
+ : RasterizerCache{rasterizer}, system{system}, emu_window{emu_window}, device{device},
+ disk_cache{system} {}
void ShaderCacheOpenGL::LoadDiskCache(const std::atomic_bool& stop_loading,
const VideoCore::DiskResourceLoadCallback& callback) {
@@ -351,60 +359,115 @@ void ShaderCacheOpenGL::LoadDiskCache(const std::atomic_bool& stop_loading,
if (!transferable) {
return;
}
- const auto [raws, usages] = *transferable;
+ const auto [raws, shader_usages] = *transferable;
auto [decompiled, dumps] = disk_cache.LoadPrecompiled();
const auto supported_formats{GetSupportedFormats()};
- const auto unspecialized{
+ const auto unspecialized_shaders{
GenerateUnspecializedShaders(stop_loading, callback, raws, decompiled)};
- if (stop_loading)
+ if (stop_loading) {
return;
+ }
- // Build shaders
- if (callback)
- callback(VideoCore::LoadCallbackStage::Build, 0, usages.size());
- for (std::size_t i = 0; i < usages.size(); ++i) {
- if (stop_loading)
- return;
+ // Track if precompiled cache was altered during loading to know if we have to serialize the
+ // virtual precompiled cache file back to the hard drive
+ bool precompiled_cache_altered = false;
- const auto& usage{usages[i]};
- LOG_INFO(Render_OpenGL, "Building shader {:016x} ({} of {})", usage.unique_identifier,
- i + 1, usages.size());
+ // Inform the frontend about shader build initialization
+ if (callback) {
+ callback(VideoCore::LoadCallbackStage::Build, 0, shader_usages.size());
+ }
+
+ std::mutex mutex;
+ std::size_t built_shaders = 0; // It doesn't have be atomic since it's used behind a mutex
+ std::atomic_bool compilation_failed = false;
- const auto& unspec{unspecialized.at(usage.unique_identifier)};
- const auto dump_it = dumps.find(usage);
+ const auto Worker = [&](Core::Frontend::GraphicsContext* context, std::size_t begin,
+ std::size_t end, const std::vector<ShaderDiskCacheUsage>& shader_usages,
+ const ShaderDumpsMap& dumps) {
+ context->MakeCurrent();
+ SCOPE_EXIT({ return context->DoneCurrent(); });
- CachedProgram shader;
- if (dump_it != dumps.end()) {
- // If the shader is dumped, attempt to load it with
- shader = GeneratePrecompiledProgram(dump_it->second, supported_formats);
+ for (std::size_t i = begin; i < end; ++i) {
+ if (stop_loading || compilation_failed) {
+ return;
+ }
+ const auto& usage{shader_usages[i]};
+ LOG_INFO(Render_OpenGL, "Building shader {:016x} (index {} of {})",
+ usage.unique_identifier, i, shader_usages.size());
+
+ const auto& unspecialized{unspecialized_shaders.at(usage.unique_identifier)};
+ const auto dump{dumps.find(usage)};
+
+ CachedProgram shader;
+ if (dump != dumps.end()) {
+ // If the shader is dumped, attempt to load it with
+ shader = GeneratePrecompiledProgram(dump->second, supported_formats);
+ if (!shader) {
+ compilation_failed = true;
+ return;
+ }
+ }
if (!shader) {
- // Invalidate the precompiled cache if a shader dumped shader was rejected
- disk_cache.InvalidatePrecompiled();
- dumps.clear();
+ shader = SpecializeShader(unspecialized.code, unspecialized.entries,
+ unspecialized.program_type, usage.bindings,
+ usage.primitive, true);
}
+
+ std::scoped_lock lock(mutex);
+ if (callback) {
+ callback(VideoCore::LoadCallbackStage::Build, ++built_shaders,
+ shader_usages.size());
+ }
+
+ precompiled_programs.emplace(usage, std::move(shader));
}
- if (!shader) {
- shader = SpecializeShader(unspec.code, unspec.entries, unspec.program_type,
- usage.bindings, usage.primitive, true);
- }
- precompiled_programs.insert({usage, std::move(shader)});
+ };
+
+ const auto num_workers{static_cast<std::size_t>(std::thread::hardware_concurrency() + 1)};
+ const std::size_t bucket_size{shader_usages.size() / num_workers};
+ std::vector<std::unique_ptr<Core::Frontend::GraphicsContext>> contexts(num_workers);
+ std::vector<std::thread> threads(num_workers);
+ for (std::size_t i = 0; i < num_workers; ++i) {
+ const bool is_last_worker = i + 1 == num_workers;
+ const std::size_t start{bucket_size * i};
+ const std::size_t end{is_last_worker ? shader_usages.size() : start + bucket_size};
+
+ // On some platforms the shared context has to be created from the GUI thread
+ contexts[i] = emu_window.CreateSharedContext();
+ threads[i] = std::thread(Worker, contexts[i].get(), start, end, shader_usages, dumps);
+ }
+ for (auto& thread : threads) {
+ thread.join();
+ }
- if (callback)
- callback(VideoCore::LoadCallbackStage::Build, i + 1, usages.size());
+ if (compilation_failed) {
+ // Invalidate the precompiled cache if a shader dumped shader was rejected
+ disk_cache.InvalidatePrecompiled();
+ dumps.clear();
+ precompiled_cache_altered = true;
+ return;
+ }
+ if (stop_loading) {
+ return;
}
// TODO(Rodrigo): Do state tracking for transferable shaders and do a dummy draw before
// precompiling them
- for (std::size_t i = 0; i < usages.size(); ++i) {
- const auto& usage{usages[i]};
+ for (std::size_t i = 0; i < shader_usages.size(); ++i) {
+ const auto& usage{shader_usages[i]};
if (dumps.find(usage) == dumps.end()) {
- const auto& program = precompiled_programs.at(usage);
+ const auto& program{precompiled_programs.at(usage)};
disk_cache.SaveDump(usage, program->handle);
+ precompiled_cache_altered = true;
}
}
+
+ if (precompiled_cache_altered) {
+ disk_cache.SaveVirtualPrecompiledFile();
+ }
}
CachedProgram ShaderCacheOpenGL::GeneratePrecompiledProgram(
@@ -437,17 +500,18 @@ std::unordered_map<u64, UnspecializedShader> ShaderCacheOpenGL::GenerateUnspecia
const std::unordered_map<u64, ShaderDiskCacheDecompiled>& decompiled) {
std::unordered_map<u64, UnspecializedShader> unspecialized;
- if (callback)
+ if (callback) {
callback(VideoCore::LoadCallbackStage::Decompile, 0, raws.size());
+ }
for (std::size_t i = 0; i < raws.size(); ++i) {
- if (stop_loading)
+ if (stop_loading) {
return {};
-
+ }
const auto& raw{raws[i]};
- const u64 unique_identifier = raw.GetUniqueIdentifier();
- const u64 calculated_hash =
- GetUniqueIdentifier(raw.GetProgramType(), raw.GetProgramCode(), raw.GetProgramCodeB());
+ const u64 unique_identifier{raw.GetUniqueIdentifier()};
+ const u64 calculated_hash{
+ GetUniqueIdentifier(raw.GetProgramType(), raw.GetProgramCode(), raw.GetProgramCodeB())};
if (unique_identifier != calculated_hash) {
LOG_ERROR(
Render_OpenGL,
@@ -464,8 +528,8 @@ std::unordered_map<u64, UnspecializedShader> ShaderCacheOpenGL::GenerateUnspecia
result = {stored_decompiled.code, stored_decompiled.entries};
} else {
// Otherwise decompile the shader at boot and save the result to the decompiled file
- result =
- CreateProgram(raw.GetProgramType(), raw.GetProgramCode(), raw.GetProgramCodeB());
+ result = CreateProgram(device, raw.GetProgramType(), raw.GetProgramCode(),
+ raw.GetProgramCodeB());
disk_cache.SaveDecompiled(unique_identifier, result.first, result.second);
}
@@ -475,45 +539,53 @@ std::unordered_map<u64, UnspecializedShader> ShaderCacheOpenGL::GenerateUnspecia
{raw.GetUniqueIdentifier(),
{std::move(result.first), std::move(result.second), raw.GetProgramType()}});
- if (callback)
+ if (callback) {
callback(VideoCore::LoadCallbackStage::Decompile, i, raws.size());
+ }
}
return unspecialized;
}
Shader ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program) {
- if (!Core::System::GetInstance().GPU().Maxwell3D().dirty_flags.shaders) {
- return last_shaders[static_cast<u32>(program)];
+ if (!system.GPU().Maxwell3D().dirty_flags.shaders) {
+ return last_shaders[static_cast<std::size_t>(program)];
}
- const VAddr program_addr{GetShaderAddress(program)};
+ auto& memory_manager{system.GPU().MemoryManager()};
+ const GPUVAddr program_addr{GetShaderAddress(system, program)};
// Look up shader in the cache based on address
- Shader shader{TryGet(program_addr)};
-
- if (!shader) {
- // No shader found - create a new one
- ProgramCode program_code = GetShaderCode(program_addr);
- ProgramCode program_code_b;
- if (program == Maxwell::ShaderProgram::VertexA) {
- program_code_b = GetShaderCode(GetShaderAddress(Maxwell::ShaderProgram::VertexB));
- }
- const u64 unique_identifier = GetUniqueIdentifier(program, program_code, program_code_b);
+ const auto host_ptr{memory_manager.GetPointer(program_addr)};
+ Shader shader{TryGet(host_ptr)};
+ if (shader) {
+ return last_shaders[static_cast<std::size_t>(program)] = shader;
+ }
- const auto found = precompiled_shaders.find(unique_identifier);
- if (found != precompiled_shaders.end()) {
- shader =
- std::make_shared<CachedShader>(program_addr, unique_identifier, program, disk_cache,
- precompiled_programs, found->second);
- } else {
- shader = std::make_shared<CachedShader>(
- program_addr, unique_identifier, program, disk_cache, precompiled_programs,
- std::move(program_code), std::move(program_code_b));
- }
- Register(shader);
+ // No shader found - create a new one
+ ProgramCode program_code{GetShaderCode(memory_manager, program_addr, host_ptr)};
+ ProgramCode program_code_b;
+ if (program == Maxwell::ShaderProgram::VertexA) {
+ const GPUVAddr program_addr_b{GetShaderAddress(system, Maxwell::ShaderProgram::VertexB)};
+ program_code_b = GetShaderCode(memory_manager, program_addr_b,
+ memory_manager.GetPointer(program_addr_b));
+ }
+
+ const u64 unique_identifier = GetUniqueIdentifier(program, program_code, program_code_b);
+ const VAddr cpu_addr{*memory_manager.GpuToCpuAddress(program_addr)};
+ const auto found = precompiled_shaders.find(unique_identifier);
+ if (found != precompiled_shaders.end()) {
+ // Create a shader from the cache
+ shader = std::make_shared<CachedShader>(cpu_addr, unique_identifier, program, disk_cache,
+ precompiled_programs, found->second, host_ptr);
+ } else {
+ // Create a shader from guest memory
+ shader = std::make_shared<CachedShader>(
+ device, cpu_addr, unique_identifier, program, disk_cache, precompiled_programs,
+ std::move(program_code), std::move(program_code_b), host_ptr);
}
+ Register(shader);
- return last_shaders[static_cast<u32>(program)] = shader;
+ return last_shaders[static_cast<std::size_t>(program)] = shader;
}
} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.h b/src/video_core/renderer_opengl/gl_shader_cache.h
index 97eed192f..09bd0761d 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.h
+++ b/src/video_core/renderer_opengl/gl_shader_cache.h
@@ -5,29 +5,33 @@
#pragma once
#include <array>
+#include <atomic>
#include <memory>
#include <set>
#include <tuple>
#include <unordered_map>
+#include <vector>
#include <glad/glad.h>
-#include "common/assert.h"
#include "common/common_types.h"
#include "video_core/rasterizer_cache.h"
-#include "video_core/renderer_base.h"
#include "video_core/renderer_opengl/gl_resource_manager.h"
#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_shader_gen.h"
namespace Core {
class System;
-} // namespace Core
+}
+
+namespace Core::Frontend {
+class EmuWindow;
+}
namespace OpenGL {
class CachedShader;
+class Device;
class RasterizerOpenGL;
struct UnspecializedShader;
@@ -39,27 +43,24 @@ using PrecompiledShaders = std::unordered_map<u64, GLShader::ProgramResult>;
class CachedShader final : public RasterizerCacheObject {
public:
- explicit CachedShader(VAddr addr, u64 unique_identifier, Maxwell::ShaderProgram program_type,
- ShaderDiskCacheOpenGL& disk_cache,
+ explicit CachedShader(const Device& device, VAddr cpu_addr, u64 unique_identifier,
+ Maxwell::ShaderProgram program_type, ShaderDiskCacheOpenGL& disk_cache,
const PrecompiledPrograms& precompiled_programs,
- ProgramCode&& program_code, ProgramCode&& program_code_b);
+ ProgramCode&& program_code, ProgramCode&& program_code_b, u8* host_ptr);
- explicit CachedShader(VAddr addr, u64 unique_identifier, Maxwell::ShaderProgram program_type,
- ShaderDiskCacheOpenGL& disk_cache,
+ explicit CachedShader(VAddr cpu_addr, u64 unique_identifier,
+ Maxwell::ShaderProgram program_type, ShaderDiskCacheOpenGL& disk_cache,
const PrecompiledPrograms& precompiled_programs,
- GLShader::ProgramResult result);
+ GLShader::ProgramResult result, u8* host_ptr);
- VAddr GetAddr() const override {
- return addr;
+ VAddr GetCpuAddr() const override {
+ return cpu_addr;
}
std::size_t GetSizeInBytes() const override {
return shader_length;
}
- // We do not have to flush this cache as things in it are never modified by us.
- void Flush() override {}
-
/// Gets the shader entries for the shader
const GLShader::ShaderEntries& GetShaderEntries() const {
return entries;
@@ -91,7 +92,8 @@ private:
ShaderDiskCacheUsage GetUsage(GLenum primitive_mode, BaseBindings base_bindings) const;
- VAddr addr{};
+ u8* host_ptr{};
+ VAddr cpu_addr{};
u64 unique_identifier{};
Maxwell::ShaderProgram program_type{};
ShaderDiskCacheOpenGL& disk_cache;
@@ -112,7 +114,8 @@ private:
class ShaderCacheOpenGL final : public RasterizerCache<Shader> {
public:
- explicit ShaderCacheOpenGL(RasterizerOpenGL& rasterizer, Core::System& system);
+ explicit ShaderCacheOpenGL(RasterizerOpenGL& rasterizer, Core::System& system,
+ Core::Frontend::EmuWindow& emu_window, const Device& device);
/// Loads disk cache for the current game
void LoadDiskCache(const std::atomic_bool& stop_loading,
@@ -121,6 +124,10 @@ public:
/// Gets the current specified shader stage program
Shader GetStageProgram(Maxwell::ShaderProgram program);
+protected:
+ // We do not have to flush this cache as things in it are never modified by us.
+ void FlushObjectInner(const Shader& object) override {}
+
private:
std::unordered_map<u64, UnspecializedShader> GenerateUnspecializedShaders(
const std::atomic_bool& stop_loading, const VideoCore::DiskResourceLoadCallback& callback,
@@ -130,11 +137,14 @@ private:
CachedProgram GeneratePrecompiledProgram(const ShaderDiskCacheDump& dump,
const std::set<GLenum>& supported_formats);
- std::array<Shader, Maxwell::MaxShaderProgram> last_shaders;
-
+ Core::System& system;
+ Core::Frontend::EmuWindow& emu_window;
+ const Device& device;
ShaderDiskCacheOpenGL disk_cache;
+
PrecompiledShaders precompiled_shaders;
PrecompiledPrograms precompiled_programs;
+ std::array<Shader, Maxwell::MaxShaderProgram> last_shaders;
};
} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
index 72ff6ac6a..7dc2e0560 100644
--- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
@@ -5,7 +5,9 @@
#include <array>
#include <string>
#include <string_view>
+#include <utility>
#include <variant>
+#include <vector>
#include <fmt/format.h>
@@ -13,12 +15,15 @@
#include "common/assert.h"
#include "common/common_types.h"
#include "video_core/engines/maxwell_3d.h"
+#include "video_core/renderer_opengl/gl_device.h"
#include "video_core/renderer_opengl/gl_rasterizer.h"
#include "video_core/renderer_opengl/gl_shader_decompiler.h"
#include "video_core/shader/shader_ir.h"
namespace OpenGL::GLShader {
+namespace {
+
using Tegra::Shader::Attribute;
using Tegra::Shader::AttributeUse;
using Tegra::Shader::Header;
@@ -26,19 +31,22 @@ using Tegra::Shader::IpaInterpMode;
using Tegra::Shader::IpaMode;
using Tegra::Shader::IpaSampleMode;
using Tegra::Shader::Register;
+
+using namespace std::string_literals;
using namespace VideoCommon::Shader;
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
using ShaderStage = Tegra::Engines::Maxwell3D::Regs::ShaderStage;
using Operation = const OperationNode&;
-enum : u32 { POSITION_VARYING_LOCATION = 0, GENERIC_VARYING_START_LOCATION = 1 };
+enum class Type { Bool, Bool2, Float, Int, Uint, HalfFloat };
+
+struct TextureAoffi {};
+using TextureArgument = std::pair<Type, Node>;
+using TextureIR = std::variant<TextureAoffi, TextureArgument>;
+
constexpr u32 MAX_CONSTBUFFER_ELEMENTS =
static_cast<u32>(RasterizerOpenGL::MaxConstbufferSize) / (4 * sizeof(float));
-constexpr u32 MAX_GLOBALMEMORY_ELEMENTS =
- static_cast<u32>(RasterizerOpenGL::MaxGlobalMemorySize) / sizeof(float);
-
-enum class Type { Bool, Bool2, Float, Int, Uint, HalfFloat };
class ShaderWriter {
public:
@@ -50,15 +58,14 @@ public:
shader_source += text;
}
- void AddLine(std::string_view text) {
- AddExpression(text);
- AddNewLine();
- }
-
- void AddLine(char character) {
- DEBUG_ASSERT(scope >= 0);
- AppendIndentation();
- shader_source += character;
+ // Forwards all arguments directly to libfmt.
+ // Note that all formatting requirements for fmt must be
+ // obeyed when using this function. (e.g. {{ must be used
+ // printing the character '{' is desirable. Ditto for }} and '}',
+ // etc).
+ template <typename... Args>
+ void AddLine(std::string_view text, Args&&... args) {
+ AddExpression(fmt::format(text, std::forward<Args>(args)...));
AddNewLine();
}
@@ -67,10 +74,8 @@ public:
shader_source += '\n';
}
- std::string GenerateTemporal() {
- std::string temporal = "tmp";
- temporal += std::to_string(temporal_index++);
- return temporal;
+ std::string GenerateTemporary() {
+ return fmt::format("tmp{}", temporary_index++);
}
std::string GetResult() {
@@ -85,19 +90,17 @@ private:
}
std::string shader_source;
- u32 temporal_index = 1;
+ u32 temporary_index = 1;
};
/// Generates code to use for a swizzle operation.
-static std::string GetSwizzle(u32 elem) {
- ASSERT(elem <= 3);
- std::string swizzle = ".";
- swizzle += "xyzw"[elem];
- return swizzle;
+constexpr const char* GetSwizzle(u32 element) {
+ constexpr std::array<const char*, 4> swizzle = {".x", ".y", ".z", ".w"};
+ return swizzle.at(element);
}
/// Translate topology
-static std::string GetTopologyName(Tegra::Shader::OutputTopology topology) {
+std::string GetTopologyName(Tegra::Shader::OutputTopology topology) {
switch (topology) {
case Tegra::Shader::OutputTopology::PointList:
return "points";
@@ -112,29 +115,57 @@ static std::string GetTopologyName(Tegra::Shader::OutputTopology topology) {
}
/// Returns true if an object has to be treated as precise
-static bool IsPrecise(Operation operand) {
- const auto& meta = operand.GetMeta();
-
+bool IsPrecise(Operation operand) {
+ const auto& meta{operand.GetMeta()};
if (const auto arithmetic = std::get_if<MetaArithmetic>(&meta)) {
return arithmetic->precise;
}
- if (const auto half_arithmetic = std::get_if<MetaHalfArithmetic>(&meta)) {
- return half_arithmetic->precise;
- }
return false;
}
-static bool IsPrecise(Node node) {
- if (const auto operation = std::get_if<OperationNode>(node)) {
+bool IsPrecise(const Node& node) {
+ if (const auto operation = std::get_if<OperationNode>(&*node)) {
return IsPrecise(*operation);
}
return false;
}
+constexpr bool IsGenericAttribute(Attribute::Index index) {
+ return index >= Attribute::Index::Attribute_0 && index <= Attribute::Index::Attribute_31;
+}
+
+constexpr Attribute::Index ToGenericAttribute(u32 value) {
+ return static_cast<Attribute::Index>(value + static_cast<u32>(Attribute::Index::Attribute_0));
+}
+
+u32 GetGenericAttributeIndex(Attribute::Index index) {
+ ASSERT(IsGenericAttribute(index));
+ return static_cast<u32>(index) - static_cast<u32>(Attribute::Index::Attribute_0);
+}
+
+constexpr const char* GetFlowStackPrefix(MetaStackClass stack) {
+ switch (stack) {
+ case MetaStackClass::Ssy:
+ return "ssy";
+ case MetaStackClass::Pbk:
+ return "pbk";
+ }
+ return {};
+}
+
+std::string FlowStackName(MetaStackClass stack) {
+ return fmt::format("{}_flow_stack", GetFlowStackPrefix(stack));
+}
+
+std::string FlowStackTopName(MetaStackClass stack) {
+ return fmt::format("{}_flow_stack_top", GetFlowStackPrefix(stack));
+}
+
class GLSLDecompiler final {
public:
- explicit GLSLDecompiler(const ShaderIR& ir, ShaderStage stage, std::string suffix)
- : ir{ir}, stage{stage}, suffix{suffix}, header{ir.GetHeader()} {}
+ explicit GLSLDecompiler(const Device& device, const ShaderIR& ir, ShaderStage stage,
+ std::string suffix)
+ : device{device}, ir{ir}, stage{stage}, suffix{suffix}, header{ir.GetHeader()} {}
void Decompile() {
DeclareVertex();
@@ -148,42 +179,45 @@ public:
DeclareConstantBuffers();
DeclareGlobalMemory();
DeclareSamplers();
+ DeclarePhysicalAttributeReader();
- code.AddLine("void execute_" + suffix + "() {");
+ code.AddLine("void execute_{}() {{", suffix);
++code.scope;
// VM's program counter
const auto first_address = ir.GetBasicBlocks().begin()->first;
- code.AddLine("uint jmp_to = " + std::to_string(first_address) + "u;");
+ code.AddLine("uint jmp_to = {}u;", first_address);
// TODO(Subv): Figure out the actual depth of the flow stack, for now it seems
// unlikely that shaders will use 20 nested SSYs and PBKs.
constexpr u32 FLOW_STACK_SIZE = 20;
- code.AddLine(fmt::format("uint flow_stack[{}];", FLOW_STACK_SIZE));
- code.AddLine("uint flow_stack_top = 0u;");
+ for (const auto stack : std::array{MetaStackClass::Ssy, MetaStackClass::Pbk}) {
+ code.AddLine("uint {}[{}];", FlowStackName(stack), FLOW_STACK_SIZE);
+ code.AddLine("uint {} = 0u;", FlowStackTopName(stack));
+ }
- code.AddLine("while (true) {");
+ code.AddLine("while (true) {{");
++code.scope;
- code.AddLine("switch (jmp_to) {");
+ code.AddLine("switch (jmp_to) {{");
for (const auto& pair : ir.GetBasicBlocks()) {
const auto [address, bb] = pair;
- code.AddLine(fmt::format("case 0x{:x}u: {{", address));
+ code.AddLine("case 0x{:x}u: {{", address);
++code.scope;
VisitBlock(bb);
--code.scope;
- code.AddLine('}');
+ code.AddLine("}}");
}
code.AddLine("default: return;");
- code.AddLine('}');
+ code.AddLine("}}");
for (std::size_t i = 0; i < 2; ++i) {
--code.scope;
- code.AddLine('}');
+ code.AddLine("}}");
}
}
@@ -200,8 +234,10 @@ public:
for (const auto& sampler : ir.GetSamplers()) {
entries.samplers.emplace_back(sampler);
}
- for (const auto& gmem : ir.GetGlobalMemoryBases()) {
- entries.global_memory_entries.emplace_back(gmem.cbuf_index, gmem.cbuf_offset);
+ for (const auto& gmem_pair : ir.GetGlobalMemory()) {
+ const auto& [base, usage] = gmem_pair;
+ entries.global_memory_entries.emplace_back(base.cbuf_index, base.cbuf_offset,
+ usage.is_read, usage.is_written);
}
entries.clip_distances = ir.GetClipDistances();
entries.shader_length = ir.GetLength();
@@ -221,21 +257,28 @@ private:
}
void DeclareGeometry() {
- if (stage != ShaderStage::Geometry)
+ if (stage != ShaderStage::Geometry) {
return;
+ }
const auto topology = GetTopologyName(header.common3.output_topology);
- const auto max_vertices = std::to_string(header.common4.max_output_vertices);
- code.AddLine("layout (" + topology + ", max_vertices = " + max_vertices + ") out;");
+ const auto max_vertices = header.common4.max_output_vertices.Value();
+ code.AddLine("layout ({}, max_vertices = {}) out;", topology, max_vertices);
code.AddNewLine();
+ code.AddLine("in gl_PerVertex {{");
+ ++code.scope;
+ code.AddLine("vec4 gl_Position;");
+ --code.scope;
+ code.AddLine("}} gl_in[];");
+
DeclareVertexRedeclarations();
}
void DeclareVertexRedeclarations() {
bool clip_distances_declared = false;
- code.AddLine("out gl_PerVertex {");
+ code.AddLine("out gl_PerVertex {{");
++code.scope;
code.AddLine("vec4 gl_Position;");
@@ -251,134 +294,158 @@ private:
}
--code.scope;
- code.AddLine("};");
+ code.AddLine("}};");
code.AddNewLine();
}
void DeclareRegisters() {
const auto& registers = ir.GetRegisters();
for (const u32 gpr : registers) {
- code.AddLine("float " + GetRegister(gpr) + " = 0;");
+ code.AddLine("float {} = 0;", GetRegister(gpr));
}
- if (!registers.empty())
+ if (!registers.empty()) {
code.AddNewLine();
+ }
}
void DeclarePredicates() {
const auto& predicates = ir.GetPredicates();
for (const auto pred : predicates) {
- code.AddLine("bool " + GetPredicate(pred) + " = false;");
+ code.AddLine("bool {} = false;", GetPredicate(pred));
}
- if (!predicates.empty())
+ if (!predicates.empty()) {
code.AddNewLine();
+ }
}
void DeclareLocalMemory() {
if (const u64 local_memory_size = header.GetLocalMemorySize(); local_memory_size > 0) {
const auto element_count = Common::AlignUp(local_memory_size, 4) / 4;
- code.AddLine("float " + GetLocalMemory() + '[' + std::to_string(element_count) + "];");
+ code.AddLine("float {}[{}];", GetLocalMemory(), element_count);
code.AddNewLine();
}
}
void DeclareInternalFlags() {
for (u32 flag = 0; flag < static_cast<u32>(InternalFlag::Amount); flag++) {
- const InternalFlag flag_code = static_cast<InternalFlag>(flag);
- code.AddLine("bool " + GetInternalFlag(flag_code) + " = false;");
+ const auto flag_code = static_cast<InternalFlag>(flag);
+ code.AddLine("bool {} = false;", GetInternalFlag(flag_code));
}
code.AddNewLine();
}
std::string GetInputFlags(AttributeUse attribute) {
- std::string out;
-
switch (attribute) {
- case AttributeUse::Constant:
- out += "flat ";
- break;
- case AttributeUse::ScreenLinear:
- out += "noperspective ";
- break;
case AttributeUse::Perspective:
// Default, Smooth
- break;
+ return {};
+ case AttributeUse::Constant:
+ return "flat ";
+ case AttributeUse::ScreenLinear:
+ return "noperspective ";
default:
- LOG_CRITICAL(HW_GPU, "Unused attribute being fetched");
- UNREACHABLE();
+ case AttributeUse::Unused:
+ UNREACHABLE_MSG("Unused attribute being fetched");
+ return {};
+ UNIMPLEMENTED_MSG("Unknown attribute usage index={}", static_cast<u32>(attribute));
+ return {};
}
- return out;
}
void DeclareInputAttributes() {
- const auto& attributes = ir.GetInputAttributes();
- for (const auto element : attributes) {
- const Attribute::Index index = element.first;
- if (index < Attribute::Index::Attribute_0 || index > Attribute::Index::Attribute_31) {
- // Skip when it's not a generic attribute
- continue;
+ if (ir.HasPhysicalAttributes()) {
+ const u32 num_inputs{GetNumPhysicalInputAttributes()};
+ for (u32 i = 0; i < num_inputs; ++i) {
+ DeclareInputAttribute(ToGenericAttribute(i), true);
}
+ code.AddNewLine();
+ return;
+ }
- // TODO(bunnei): Use proper number of elements for these
- u32 idx = static_cast<u32>(index) - static_cast<u32>(Attribute::Index::Attribute_0);
- if (stage != ShaderStage::Vertex) {
- // If inputs are varyings, add an offset
- idx += GENERIC_VARYING_START_LOCATION;
+ const auto& attributes = ir.GetInputAttributes();
+ for (const auto index : attributes) {
+ if (IsGenericAttribute(index)) {
+ DeclareInputAttribute(index, false);
}
+ }
+ if (!attributes.empty()) {
+ code.AddNewLine();
+ }
+ }
- std::string attr = GetInputAttribute(index);
- if (stage == ShaderStage::Geometry) {
- attr = "gs_" + attr + "[]";
- }
- std::string suffix;
- if (stage == ShaderStage::Fragment) {
- const auto input_mode =
- header.ps.GetAttributeUse(idx - GENERIC_VARYING_START_LOCATION);
- suffix = GetInputFlags(input_mode);
+ void DeclareInputAttribute(Attribute::Index index, bool skip_unused) {
+ const u32 location{GetGenericAttributeIndex(index)};
+
+ std::string name{GetInputAttribute(index)};
+ if (stage == ShaderStage::Geometry) {
+ name = "gs_" + name + "[]";
+ }
+
+ std::string suffix;
+ if (stage == ShaderStage::Fragment) {
+ const auto input_mode{header.ps.GetAttributeUse(location)};
+ if (skip_unused && input_mode == AttributeUse::Unused) {
+ return;
}
- code.AddLine("layout (location = " + std::to_string(idx) + ") " + suffix + "in vec4 " +
- attr + ';');
+ suffix = GetInputFlags(input_mode);
}
- if (!attributes.empty())
- code.AddNewLine();
+
+ code.AddLine("layout (location = {}) {} in vec4 {};", location, suffix, name);
}
void DeclareOutputAttributes() {
+ if (ir.HasPhysicalAttributes() && stage != ShaderStage::Fragment) {
+ for (u32 i = 0; i < GetNumPhysicalVaryings(); ++i) {
+ DeclareOutputAttribute(ToGenericAttribute(i));
+ }
+ code.AddNewLine();
+ return;
+ }
+
const auto& attributes = ir.GetOutputAttributes();
for (const auto index : attributes) {
- if (index < Attribute::Index::Attribute_0 || index > Attribute::Index::Attribute_31) {
- // Skip when it's not a generic attribute
- continue;
+ if (IsGenericAttribute(index)) {
+ DeclareOutputAttribute(index);
}
- // TODO(bunnei): Use proper number of elements for these
- const auto idx = static_cast<u32>(index) -
- static_cast<u32>(Attribute::Index::Attribute_0) +
- GENERIC_VARYING_START_LOCATION;
- code.AddLine("layout (location = " + std::to_string(idx) + ") out vec4 " +
- GetOutputAttribute(index) + ';');
- }
- if (!attributes.empty())
+ }
+ if (!attributes.empty()) {
code.AddNewLine();
+ }
+ }
+
+ void DeclareOutputAttribute(Attribute::Index index) {
+ const u32 location{GetGenericAttributeIndex(index)};
+ code.AddLine("layout (location = {}) out vec4 {};", location, GetOutputAttribute(index));
}
void DeclareConstantBuffers() {
for (const auto& entry : ir.GetConstantBuffers()) {
const auto [index, size] = entry;
- code.AddLine("layout (std140, binding = CBUF_BINDING_" + std::to_string(index) +
- ") uniform " + GetConstBufferBlock(index) + " {");
- code.AddLine(" vec4 " + GetConstBuffer(index) + "[MAX_CONSTBUFFER_ELEMENTS];");
- code.AddLine("};");
+ code.AddLine("layout (std140, binding = CBUF_BINDING_{}) uniform {} {{", index,
+ GetConstBufferBlock(index));
+ code.AddLine(" vec4 {}[MAX_CONSTBUFFER_ELEMENTS];", GetConstBuffer(index));
+ code.AddLine("}};");
code.AddNewLine();
}
}
void DeclareGlobalMemory() {
- for (const auto& entry : ir.GetGlobalMemoryBases()) {
- const std::string binding =
- fmt::format("GMEM_BINDING_{}_{}", entry.cbuf_index, entry.cbuf_offset);
- code.AddLine("layout (std430, binding = " + binding + ") buffer " +
- GetGlobalMemoryBlock(entry) + " {");
- code.AddLine(" float " + GetGlobalMemory(entry) + "[MAX_GLOBALMEMORY_ELEMENTS];");
- code.AddLine("};");
+ for (const auto& gmem : ir.GetGlobalMemory()) {
+ const auto& [base, usage] = gmem;
+
+ // Since we don't know how the shader will use the shader, hint the driver to disable as
+ // much optimizations as possible
+ std::string qualifier = "coherent volatile";
+ if (usage.is_read && !usage.is_written) {
+ qualifier += " readonly";
+ } else if (usage.is_written && !usage.is_read) {
+ qualifier += " writeonly";
+ }
+
+ code.AddLine("layout (std430, binding = GMEM_BINDING_{}_{}) {} buffer {} {{",
+ base.cbuf_index, base.cbuf_offset, qualifier, GetGlobalMemoryBlock(base));
+ code.AddLine(" float {}[];", GetGlobalMemory(base));
+ code.AddLine("}};");
code.AddNewLine();
}
}
@@ -386,7 +453,7 @@ private:
void DeclareSamplers() {
const auto& samplers = ir.GetSamplers();
for (const auto& sampler : samplers) {
- std::string sampler_type = [&]() {
+ std::string sampler_type = [&sampler] {
switch (sampler.GetType()) {
case Tegra::Shader::TextureType::Texture1D:
return "sampler1D";
@@ -401,51 +468,95 @@ private:
return "sampler2D";
}
}();
- if (sampler.IsArray())
+ if (sampler.IsArray()) {
sampler_type += "Array";
- if (sampler.IsShadow())
+ }
+ if (sampler.IsShadow()) {
sampler_type += "Shadow";
+ }
- code.AddLine("layout (binding = SAMPLER_BINDING_" + std::to_string(sampler.GetIndex()) +
- ") uniform " + sampler_type + ' ' + GetSampler(sampler) + ';');
+ code.AddLine("layout (binding = SAMPLER_BINDING_{}) uniform {} {};", sampler.GetIndex(),
+ sampler_type, GetSampler(sampler));
}
- if (!samplers.empty())
+ if (!samplers.empty()) {
code.AddNewLine();
+ }
+ }
+
+ void DeclarePhysicalAttributeReader() {
+ if (!ir.HasPhysicalAttributes()) {
+ return;
+ }
+ code.AddLine("float readPhysicalAttribute(uint physical_address) {{");
+ ++code.scope;
+ code.AddLine("switch (physical_address) {{");
+
+ // Just declare generic attributes for now.
+ const auto num_attributes{static_cast<u32>(GetNumPhysicalInputAttributes())};
+ for (u32 index = 0; index < num_attributes; ++index) {
+ const auto attribute{ToGenericAttribute(index)};
+ for (u32 element = 0; element < 4; ++element) {
+ constexpr u32 generic_base{0x80};
+ constexpr u32 generic_stride{16};
+ constexpr u32 element_stride{4};
+ const u32 address{generic_base + index * generic_stride + element * element_stride};
+
+ const bool declared{stage != ShaderStage::Fragment ||
+ header.ps.GetAttributeUse(index) != AttributeUse::Unused};
+ const std::string value{declared ? ReadAttribute(attribute, element) : "0"};
+ code.AddLine("case 0x{:x}: return {};", address, value);
+ }
+ }
+
+ code.AddLine("default: return 0;");
+
+ code.AddLine("}}");
+ --code.scope;
+ code.AddLine("}}");
+ code.AddNewLine();
}
void VisitBlock(const NodeBlock& bb) {
- for (const Node node : bb) {
+ for (const auto& node : bb) {
if (const std::string expr = Visit(node); !expr.empty()) {
code.AddLine(expr);
}
}
}
- std::string Visit(Node node) {
- if (const auto operation = std::get_if<OperationNode>(node)) {
+ std::string Visit(const Node& node) {
+ if (const auto operation = std::get_if<OperationNode>(&*node)) {
const auto operation_index = static_cast<std::size_t>(operation->GetCode());
+ if (operation_index >= operation_decompilers.size()) {
+ UNREACHABLE_MSG("Out of bounds operation: {}", operation_index);
+ return {};
+ }
const auto decompiler = operation_decompilers[operation_index];
if (decompiler == nullptr) {
- UNREACHABLE_MSG("Operation decompiler {} not defined", operation_index);
+ UNREACHABLE_MSG("Undefined operation: {}", operation_index);
+ return {};
}
return (this->*decompiler)(*operation);
+ }
- } else if (const auto gpr = std::get_if<GprNode>(node)) {
+ if (const auto gpr = std::get_if<GprNode>(&*node)) {
const u32 index = gpr->GetIndex();
if (index == Register::ZeroIndex) {
return "0";
}
return GetRegister(index);
+ }
- } else if (const auto immediate = std::get_if<ImmediateNode>(node)) {
+ if (const auto immediate = std::get_if<ImmediateNode>(&*node)) {
const u32 value = immediate->GetValue();
if (value < 10) {
// For eyecandy avoid using hex numbers on single digits
return fmt::format("utof({}u)", immediate->GetValue());
}
return fmt::format("utof(0x{:x}u)", immediate->GetValue());
+ }
- } else if (const auto predicate = std::get_if<PredicateNode>(node)) {
+ if (const auto predicate = std::get_if<PredicateNode>(&*node)) {
const auto value = [&]() -> std::string {
switch (const auto index = predicate->GetIndex(); index) {
case Tegra::Shader::Pred::UnusedIndex:
@@ -457,127 +568,161 @@ private:
}
}();
if (predicate->IsNegated()) {
- return "!(" + value + ')';
+ return fmt::format("!({})", value);
}
return value;
+ }
- } else if (const auto abuf = std::get_if<AbufNode>(node)) {
- const auto attribute = abuf->GetIndex();
- const auto element = abuf->GetElement();
-
- const auto GeometryPass = [&](const std::string& name) {
- if (stage == ShaderStage::Geometry && abuf->GetBuffer()) {
- // TODO(Rodrigo): Guard geometry inputs against out of bound reads. Some games
- // set an 0x80000000 index for those and the shader fails to build. Find out why
- // this happens and what's its intent.
- return "gs_" + name + "[ftou(" + Visit(abuf->GetBuffer()) +
- ") % MAX_VERTEX_INPUT]";
- }
- return name;
- };
-
- switch (attribute) {
- case Attribute::Index::Position:
- if (stage != ShaderStage::Fragment) {
- return GeometryPass("position") + GetSwizzle(element);
- } else {
- return element == 3 ? "1.0f" : "gl_FragCoord" + GetSwizzle(element);
- }
- case Attribute::Index::PointCoord:
- switch (element) {
- case 0:
- return "gl_PointCoord.x";
- case 1:
- return "gl_PointCoord.y";
- case 2:
- case 3:
- return "0";
- }
- UNREACHABLE();
- return "0";
- case Attribute::Index::TessCoordInstanceIDVertexID:
- // TODO(Subv): Find out what the values are for the first two elements when inside a
- // vertex shader, and what's the value of the fourth element when inside a Tess Eval
- // shader.
- ASSERT(stage == ShaderStage::Vertex);
- switch (element) {
- case 2:
- // Config pack's first value is instance_id.
- return "uintBitsToFloat(config_pack[0])";
- case 3:
- return "uintBitsToFloat(gl_VertexID)";
- }
- UNIMPLEMENTED_MSG("Unmanaged TessCoordInstanceIDVertexID element={}", element);
- return "0";
- case Attribute::Index::FrontFacing:
- // TODO(Subv): Find out what the values are for the other elements.
- ASSERT(stage == ShaderStage::Fragment);
- switch (element) {
- case 3:
- return "itof(gl_FrontFacing ? -1 : 0)";
- }
- UNIMPLEMENTED_MSG("Unmanaged FrontFacing element={}", element);
- return "0";
- default:
- if (attribute >= Attribute::Index::Attribute_0 &&
- attribute <= Attribute::Index::Attribute_31) {
- return GeometryPass(GetInputAttribute(attribute)) + GetSwizzle(element);
- }
- break;
+ if (const auto abuf = std::get_if<AbufNode>(&*node)) {
+ UNIMPLEMENTED_IF_MSG(abuf->IsPhysicalBuffer() && stage == ShaderStage::Geometry,
+ "Physical attributes in geometry shaders are not implemented");
+ if (abuf->IsPhysicalBuffer()) {
+ return fmt::format("readPhysicalAttribute(ftou({}))",
+ Visit(abuf->GetPhysicalAddress()));
}
- UNIMPLEMENTED_MSG("Unhandled input attribute: {}", static_cast<u32>(attribute));
+ return ReadAttribute(abuf->GetIndex(), abuf->GetElement(), abuf->GetBuffer());
+ }
- } else if (const auto cbuf = std::get_if<CbufNode>(node)) {
+ if (const auto cbuf = std::get_if<CbufNode>(&*node)) {
const Node offset = cbuf->GetOffset();
- if (const auto immediate = std::get_if<ImmediateNode>(offset)) {
+ 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");
return fmt::format("{}[{}][{}]", GetConstBuffer(cbuf->GetIndex()),
offset_imm / (4 * 4), (offset_imm / 4) % 4);
+ }
- } else if (std::holds_alternative<OperationNode>(*offset)) {
+ if (std::holds_alternative<OperationNode>(*offset)) {
// Indirect access
- const std::string final_offset = code.GenerateTemporal();
- code.AddLine("uint " + final_offset + " = (ftou(" + Visit(offset) + ") / 4) & " +
- std::to_string(MAX_CONSTBUFFER_ELEMENTS - 1) + ';');
- return fmt::format("{}[{} / 4][{} % 4]", GetConstBuffer(cbuf->GetIndex()),
- final_offset, final_offset);
+ const std::string final_offset = code.GenerateTemporary();
+ code.AddLine("uint {} = ftou({}) >> 2;", final_offset, Visit(offset));
- } else {
- UNREACHABLE_MSG("Unmanaged offset node type");
+ if (!device.HasComponentIndexingBug()) {
+ return fmt::format("{}[{} >> 2][{} & 3]", GetConstBuffer(cbuf->GetIndex()),
+ final_offset, final_offset);
+ }
+
+ // AMD's proprietary GLSL compiler emits ill code for variable component access.
+ // To bypass this driver bug generate 4 ifs, one per each component.
+ const std::string pack = code.GenerateTemporary();
+ code.AddLine("vec4 {} = {}[{} >> 2];", pack, GetConstBuffer(cbuf->GetIndex()),
+ final_offset);
+
+ const std::string result = code.GenerateTemporary();
+ code.AddLine("float {};", result);
+ for (u32 swizzle = 0; swizzle < 4; ++swizzle) {
+ code.AddLine("if (({} & 3) == {}) {} = {}{};", final_offset, swizzle, result,
+ pack, GetSwizzle(swizzle));
+ }
+ return result;
}
- } else if (const auto gmem = std::get_if<GmemNode>(node)) {
+ UNREACHABLE_MSG("Unmanaged offset node type");
+ }
+
+ if (const auto gmem = std::get_if<GmemNode>(&*node)) {
const std::string real = Visit(gmem->GetRealAddress());
const std::string base = Visit(gmem->GetBaseAddress());
- const std::string final_offset = "(ftou(" + real + ") - ftou(" + base + ")) / 4";
+ const std::string final_offset = fmt::format("(ftou({}) - ftou({})) / 4", real, base);
return fmt::format("{}[{}]", GetGlobalMemory(gmem->GetDescriptor()), final_offset);
+ }
- } else if (const auto lmem = std::get_if<LmemNode>(node)) {
+ if (const auto lmem = std::get_if<LmemNode>(&*node)) {
return fmt::format("{}[ftou({}) / 4]", GetLocalMemory(), Visit(lmem->GetAddress()));
+ }
- } else if (const auto internal_flag = std::get_if<InternalFlagNode>(node)) {
+ if (const auto internal_flag = std::get_if<InternalFlagNode>(&*node)) {
return GetInternalFlag(internal_flag->GetFlag());
+ }
- } else if (const auto conditional = std::get_if<ConditionalNode>(node)) {
+ if (const auto conditional = std::get_if<ConditionalNode>(&*node)) {
// It's invalid to call conditional on nested nodes, use an operation instead
- code.AddLine("if (" + Visit(conditional->GetCondition()) + ") {");
+ code.AddLine("if ({}) {{", Visit(conditional->GetCondition()));
++code.scope;
VisitBlock(conditional->GetCode());
--code.scope;
- code.AddLine('}');
+ code.AddLine("}}");
return {};
+ }
- } else if (const auto comment = std::get_if<CommentNode>(node)) {
+ if (const auto comment = std::get_if<CommentNode>(&*node)) {
return "// " + comment->GetText();
}
+
UNREACHABLE();
return {};
}
+ std::string ReadAttribute(Attribute::Index attribute, u32 element, const Node& buffer = {}) {
+ const auto GeometryPass = [&](std::string_view name) {
+ if (stage == ShaderStage::Geometry && buffer) {
+ // TODO(Rodrigo): Guard geometry inputs against out of bound reads. Some games
+ // set an 0x80000000 index for those and the shader fails to build. Find out why
+ // this happens and what's its intent.
+ return fmt::format("gs_{}[ftou({}) % MAX_VERTEX_INPUT]", name, Visit(buffer));
+ }
+ return std::string(name);
+ };
+
+ switch (attribute) {
+ case Attribute::Index::Position:
+ switch (stage) {
+ case ShaderStage::Geometry:
+ return fmt::format("gl_in[ftou({})].gl_Position{}", Visit(buffer),
+ GetSwizzle(element));
+ case ShaderStage::Fragment:
+ return element == 3 ? "1.0f" : ("gl_FragCoord"s + GetSwizzle(element));
+ default:
+ UNREACHABLE();
+ }
+ case Attribute::Index::PointCoord:
+ switch (element) {
+ case 0:
+ return "gl_PointCoord.x";
+ case 1:
+ return "gl_PointCoord.y";
+ case 2:
+ case 3:
+ return "0";
+ }
+ UNREACHABLE();
+ return "0";
+ case Attribute::Index::TessCoordInstanceIDVertexID:
+ // TODO(Subv): Find out what the values are for the first two elements when inside a
+ // vertex shader, and what's the value of the fourth element when inside a Tess Eval
+ // shader.
+ ASSERT(stage == ShaderStage::Vertex);
+ switch (element) {
+ case 2:
+ // Config pack's first value is instance_id.
+ return "uintBitsToFloat(config_pack[0])";
+ case 3:
+ return "uintBitsToFloat(gl_VertexID)";
+ }
+ UNIMPLEMENTED_MSG("Unmanaged TessCoordInstanceIDVertexID element={}", element);
+ return "0";
+ case Attribute::Index::FrontFacing:
+ // TODO(Subv): Find out what the values are for the other elements.
+ ASSERT(stage == ShaderStage::Fragment);
+ switch (element) {
+ case 3:
+ return "itof(gl_FrontFacing ? -1 : 0)";
+ }
+ UNIMPLEMENTED_MSG("Unmanaged FrontFacing element={}", element);
+ return "0";
+ default:
+ if (IsGenericAttribute(attribute)) {
+ return GeometryPass(GetInputAttribute(attribute)) + GetSwizzle(element);
+ }
+ break;
+ }
+ UNIMPLEMENTED_MSG("Unhandled input attribute: {}", static_cast<u32>(attribute));
+ return "0";
+ }
+
std::string ApplyPrecise(Operation operation, const std::string& value) {
if (!IsPrecise(operation)) {
return value;
@@ -585,9 +730,9 @@ private:
// There's a bug in NVidia's proprietary drivers that makes precise fail on fragment shaders
const std::string precise = stage != ShaderStage::Fragment ? "precise " : "";
- const std::string temporal = code.GenerateTemporal();
- code.AddLine(precise + "float " + temporal + " = " + value + ';');
- return temporal;
+ const std::string temporary = code.GenerateTemporary();
+ code.AddLine("{}float {} = {};", precise, temporary, value);
+ return temporary;
}
std::string VisitOperand(Operation operation, std::size_t operand_index) {
@@ -599,34 +744,13 @@ private:
return Visit(operand);
}
- const std::string temporal = code.GenerateTemporal();
- code.AddLine("float " + temporal + " = " + Visit(operand) + ';');
- return temporal;
+ const std::string temporary = code.GenerateTemporary();
+ code.AddLine("float {} = {};", temporary, Visit(operand));
+ return temporary;
}
std::string VisitOperand(Operation operation, std::size_t operand_index, Type type) {
- std::string value = VisitOperand(operation, operand_index);
- switch (type) {
- case Type::HalfFloat: {
- const auto half_meta = std::get_if<MetaHalfArithmetic>(&operation.GetMeta());
- if (!half_meta) {
- value = "toHalf2(" + value + ')';
- }
-
- switch (half_meta->types.at(operand_index)) {
- case Tegra::Shader::HalfType::H0_H1:
- return "toHalf2(" + value + ')';
- case Tegra::Shader::HalfType::F32:
- return "vec2(" + value + ')';
- case Tegra::Shader::HalfType::H0_H0:
- return "vec2(toHalf2(" + value + ")[0])";
- case Tegra::Shader::HalfType::H1_H1:
- return "vec2(toHalf2(" + value + ")[1])";
- }
- }
- default:
- return CastOperand(value, type);
- }
+ return CastOperand(VisitOperand(operation, operand_index), type);
}
std::string CastOperand(const std::string& value, Type type) const {
@@ -636,33 +760,32 @@ private:
case Type::Float:
return value;
case Type::Int:
- return "ftoi(" + value + ')';
+ return fmt::format("ftoi({})", value);
case Type::Uint:
- return "ftou(" + value + ')';
+ return fmt::format("ftou({})", value);
case Type::HalfFloat:
- // Can't be handled as a stand-alone value
- UNREACHABLE();
- return value;
+ return fmt::format("toHalf2({})", value);
}
UNREACHABLE();
return value;
}
- std::string BitwiseCastResult(std::string value, Type type, bool needs_parenthesis = false) {
+ std::string BitwiseCastResult(const std::string& value, Type type,
+ bool needs_parenthesis = false) {
switch (type) {
case Type::Bool:
case Type::Bool2:
case Type::Float:
if (needs_parenthesis) {
- return '(' + value + ')';
+ return fmt::format("({})", value);
}
return value;
case Type::Int:
- return "itof(" + value + ')';
+ return fmt::format("itof({})", value);
case Type::Uint:
- return "utof(" + value + ')';
+ return fmt::format("utof({})", value);
case Type::HalfFloat:
- return "fromHalf2(" + value + ')';
+ return fmt::format("fromHalf2({})", value);
}
UNREACHABLE();
return value;
@@ -670,27 +793,27 @@ private:
std::string GenerateUnary(Operation operation, const std::string& func, Type result_type,
Type type_a, bool needs_parenthesis = true) {
- return ApplyPrecise(operation,
- BitwiseCastResult(func + '(' + VisitOperand(operation, 0, type_a) + ')',
- result_type, needs_parenthesis));
+ const std::string op_str = fmt::format("{}({})", func, VisitOperand(operation, 0, type_a));
+
+ return ApplyPrecise(operation, BitwiseCastResult(op_str, result_type, needs_parenthesis));
}
std::string GenerateBinaryInfix(Operation operation, const std::string& func, Type result_type,
Type type_a, Type type_b) {
const std::string op_a = VisitOperand(operation, 0, type_a);
const std::string op_b = VisitOperand(operation, 1, type_b);
+ const std::string op_str = fmt::format("({} {} {})", op_a, func, op_b);
- return ApplyPrecise(
- operation, BitwiseCastResult('(' + op_a + ' ' + func + ' ' + op_b + ')', result_type));
+ return ApplyPrecise(operation, BitwiseCastResult(op_str, result_type));
}
std::string GenerateBinaryCall(Operation operation, const std::string& func, Type result_type,
Type type_a, Type type_b) {
const std::string op_a = VisitOperand(operation, 0, type_a);
const std::string op_b = VisitOperand(operation, 1, type_b);
+ const std::string op_str = fmt::format("{}({}, {})", func, op_a, op_b);
- return ApplyPrecise(operation,
- BitwiseCastResult(func + '(' + op_a + ", " + op_b + ')', result_type));
+ return ApplyPrecise(operation, BitwiseCastResult(op_str, result_type));
}
std::string GenerateTernary(Operation operation, const std::string& func, Type result_type,
@@ -698,10 +821,9 @@ private:
const std::string op_a = VisitOperand(operation, 0, type_a);
const std::string op_b = VisitOperand(operation, 1, type_b);
const std::string op_c = VisitOperand(operation, 2, type_c);
+ const std::string op_str = fmt::format("{}({}, {}, {})", func, op_a, op_b, op_c);
- return ApplyPrecise(
- operation,
- BitwiseCastResult(func + '(' + op_a + ", " + op_b + ", " + op_c + ')', result_type));
+ return ApplyPrecise(operation, BitwiseCastResult(op_str, result_type));
}
std::string GenerateQuaternary(Operation operation, const std::string& func, Type result_type,
@@ -710,14 +832,13 @@ private:
const std::string op_b = VisitOperand(operation, 1, type_b);
const std::string op_c = VisitOperand(operation, 2, type_c);
const std::string op_d = VisitOperand(operation, 3, type_d);
+ const std::string op_str = fmt::format("{}({}, {}, {}, {})", func, op_a, op_b, op_c, op_d);
- return ApplyPrecise(operation, BitwiseCastResult(func + '(' + op_a + ", " + op_b + ", " +
- op_c + ", " + op_d + ')',
- result_type));
+ return ApplyPrecise(operation, BitwiseCastResult(op_str, result_type));
}
- std::string GenerateTexture(Operation operation, const std::string& func,
- bool is_extra_int = false) {
+ std::string GenerateTexture(Operation operation, const std::string& function_suffix,
+ const std::vector<TextureIR>& extras) {
constexpr std::array<const char*, 4> coord_constructors = {"float", "vec2", "vec3", "vec4"};
const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
@@ -727,75 +848,128 @@ private:
const bool has_array = meta->sampler.IsArray();
const bool has_shadow = meta->sampler.IsShadow();
- std::string expr = func;
- expr += '(';
- expr += GetSampler(meta->sampler);
- expr += ", ";
-
+ std::string expr = "texture" + function_suffix;
+ if (!meta->aoffi.empty()) {
+ expr += "Offset";
+ }
+ expr += '(' + GetSampler(meta->sampler) + ", ";
expr += coord_constructors.at(count + (has_array ? 1 : 0) + (has_shadow ? 1 : 0) - 1);
expr += '(';
for (std::size_t i = 0; i < count; ++i) {
expr += Visit(operation[i]);
const std::size_t next = i + 1;
- if (next < count || has_array || has_shadow)
+ if (next < count)
expr += ", ";
}
if (has_array) {
- expr += "float(ftoi(" + Visit(meta->array) + "))";
+ expr += ", float(ftoi(" + Visit(meta->array) + "))";
}
if (has_shadow) {
- if (has_array)
- expr += ", ";
- expr += Visit(meta->depth_compare);
+ expr += ", " + Visit(meta->depth_compare);
}
expr += ')';
- for (const Node extra : meta->extras) {
- expr += ", ";
- if (is_extra_int) {
- if (const auto immediate = std::get_if<ImmediateNode>(extra)) {
- // Inline the string as an immediate integer in GLSL (some extra arguments are
- // required to be constant)
- expr += std::to_string(static_cast<s32>(immediate->GetValue()));
- } else {
- expr += "ftoi(" + Visit(extra) + ')';
- }
+ for (const auto& variant : extras) {
+ if (const auto argument = std::get_if<TextureArgument>(&variant)) {
+ expr += GenerateTextureArgument(*argument);
+ } else if (std::get_if<TextureAoffi>(&variant)) {
+ expr += GenerateTextureAoffi(meta->aoffi);
+ } else {
+ UNREACHABLE();
+ }
+ }
+
+ return expr + ')';
+ }
+
+ std::string GenerateTextureArgument(TextureArgument argument) {
+ const auto [type, operand] = argument;
+ if (operand == nullptr) {
+ return {};
+ }
+
+ std::string expr = ", ";
+ switch (type) {
+ case Type::Int:
+ if (const auto immediate = std::get_if<ImmediateNode>(&*operand)) {
+ // Inline the string as an immediate integer in GLSL (some extra arguments are
+ // required to be constant)
+ expr += std::to_string(static_cast<s32>(immediate->GetValue()));
} else {
- expr += Visit(extra);
+ expr += fmt::format("ftoi({})", Visit(operand));
}
+ break;
+ case Type::Float:
+ expr += Visit(operand);
+ break;
+ default: {
+ const auto type_int = static_cast<u32>(type);
+ UNIMPLEMENTED_MSG("Unimplemented extra type={}", type_int);
+ expr += '0';
+ break;
}
+ }
+ return expr;
+ }
+ std::string GenerateTextureAoffi(const std::vector<Node>& aoffi) {
+ if (aoffi.empty()) {
+ return {};
+ }
+ constexpr std::array<const char*, 3> coord_constructors = {"int", "ivec2", "ivec3"};
+ std::string expr = ", ";
+ expr += coord_constructors.at(aoffi.size() - 1);
+ expr += '(';
+
+ for (std::size_t index = 0; index < aoffi.size(); ++index) {
+ const auto operand{aoffi.at(index)};
+ if (const auto immediate = std::get_if<ImmediateNode>(&*operand)) {
+ // Inline the string as an immediate integer in GLSL (AOFFI arguments are required
+ // to be constant by the standard).
+ expr += std::to_string(static_cast<s32>(immediate->GetValue()));
+ } else if (device.HasVariableAoffi()) {
+ // Avoid using variable AOFFI on unsupported devices.
+ expr += fmt::format("ftoi({})", Visit(operand));
+ } else {
+ // Insert 0 on devices not supporting variable AOFFI.
+ expr += '0';
+ }
+ if (index + 1 < aoffi.size()) {
+ expr += ", ";
+ }
+ }
expr += ')';
+
return expr;
}
std::string Assign(Operation operation) {
- const Node dest = operation[0];
- const Node src = operation[1];
+ const Node& dest = operation[0];
+ const Node& src = operation[1];
std::string target;
- if (const auto gpr = std::get_if<GprNode>(dest)) {
+ if (const auto gpr = std::get_if<GprNode>(&*dest)) {
if (gpr->GetIndex() == Register::ZeroIndex) {
// Writing to Register::ZeroIndex is a no op
return {};
}
target = GetRegister(gpr->GetIndex());
+ } else if (const auto abuf = std::get_if<AbufNode>(&*dest)) {
+ UNIMPLEMENTED_IF(abuf->IsPhysicalBuffer());
- } else if (const auto abuf = std::get_if<AbufNode>(dest)) {
target = [&]() -> std::string {
switch (const auto attribute = abuf->GetIndex(); abuf->GetIndex()) {
case Attribute::Index::Position:
- return "position" + GetSwizzle(abuf->GetElement());
+ return "gl_Position"s + GetSwizzle(abuf->GetElement());
case Attribute::Index::PointSize:
return "gl_PointSize";
case Attribute::Index::ClipDistances0123:
- return "gl_ClipDistance[" + std::to_string(abuf->GetElement()) + ']';
+ return fmt::format("gl_ClipDistance[{}]", abuf->GetElement());
case Attribute::Index::ClipDistances4567:
- return "gl_ClipDistance[" + std::to_string(abuf->GetElement() + 4) + ']';
+ return fmt::format("gl_ClipDistance[{}]", abuf->GetElement() + 4);
default:
- if (attribute >= Attribute::Index::Attribute_0 &&
- attribute <= Attribute::Index::Attribute_31) {
+ if (IsGenericAttribute(attribute)) {
return GetOutputAttribute(attribute) + GetSwizzle(abuf->GetElement());
}
UNIMPLEMENTED_MSG("Unhandled output attribute: {}",
@@ -803,29 +977,21 @@ private:
return "0";
}
}();
-
- } else if (const auto lmem = std::get_if<LmemNode>(dest)) {
- target = GetLocalMemory() + "[ftou(" + Visit(lmem->GetAddress()) + ") / 4]";
-
+ } else if (const auto lmem = std::get_if<LmemNode>(&*dest)) {
+ target = fmt::format("{}[ftou({}) / 4]", GetLocalMemory(), Visit(lmem->GetAddress()));
+ } else if (const auto gmem = std::get_if<GmemNode>(&*dest)) {
+ const std::string real = Visit(gmem->GetRealAddress());
+ const std::string base = Visit(gmem->GetBaseAddress());
+ const std::string final_offset = fmt::format("(ftou({}) - ftou({})) / 4", real, base);
+ target = fmt::format("{}[{}]", GetGlobalMemory(gmem->GetDescriptor()), final_offset);
} else {
UNREACHABLE_MSG("Assign called without a proper target");
}
- code.AddLine(target + " = " + Visit(src) + ';');
+ code.AddLine("{} = {};", target, Visit(src));
return {};
}
- std::string Composite(Operation operation) {
- std::string value = "vec4(";
- for (std::size_t i = 0; i < 4; ++i) {
- value += Visit(operation[i]);
- if (i < 3)
- value += ", ";
- }
- value += ')';
- return value;
- }
-
template <Type type>
std::string Add(Operation operation) {
return GenerateBinaryInfix(operation, "+", type, type, type);
@@ -875,8 +1041,9 @@ private:
const std::string condition = Visit(operation[0]);
const std::string true_case = Visit(operation[1]);
const std::string false_case = Visit(operation[2]);
- return ApplyPrecise(operation,
- '(' + condition + " ? " + true_case + " : " + false_case + ')');
+ const std::string op_str = fmt::format("({} ? {} : {})", condition, true_case, false_case);
+
+ return ApplyPrecise(operation, op_str);
}
std::string FCos(Operation operation) {
@@ -940,9 +1107,9 @@ private:
std::string ILogicalShiftRight(Operation operation) {
const std::string op_a = VisitOperand(operation, 0, Type::Uint);
const std::string op_b = VisitOperand(operation, 1, Type::Uint);
+ const std::string op_str = fmt::format("int({} >> {})", op_a, op_b);
- return ApplyPrecise(operation,
- BitwiseCastResult("int(" + op_a + " >> " + op_b + ')', Type::Int));
+ return ApplyPrecise(operation, BitwiseCastResult(op_str, Type::Int));
}
std::string IArithmeticShiftRight(Operation operation) {
@@ -998,30 +1165,60 @@ private:
}
std::string HNegate(Operation operation) {
- const auto GetNegate = [&](std::size_t index) -> std::string {
+ const auto GetNegate = [&](std::size_t index) {
return VisitOperand(operation, index, Type::Bool) + " ? -1 : 1";
};
- const std::string value = '(' + VisitOperand(operation, 0, Type::HalfFloat) + " * vec2(" +
- GetNegate(1) + ", " + GetNegate(2) + "))";
+ const std::string value =
+ fmt::format("({} * vec2({}, {}))", VisitOperand(operation, 0, Type::HalfFloat),
+ GetNegate(1), GetNegate(2));
return BitwiseCastResult(value, Type::HalfFloat);
}
+ std::string HClamp(Operation operation) {
+ const std::string value = VisitOperand(operation, 0, Type::HalfFloat);
+ const std::string min = VisitOperand(operation, 1, Type::Float);
+ const std::string max = VisitOperand(operation, 2, Type::Float);
+ const std::string clamped = fmt::format("clamp({}, vec2({}), vec2({}))", value, min, max);
+
+ return ApplyPrecise(operation, BitwiseCastResult(clamped, Type::HalfFloat));
+ }
+
+ std::string HUnpack(Operation operation) {
+ const std::string operand{VisitOperand(operation, 0, Type::HalfFloat)};
+ const auto value = [&]() -> std::string {
+ switch (std::get<Tegra::Shader::HalfType>(operation.GetMeta())) {
+ case Tegra::Shader::HalfType::H0_H1:
+ return operand;
+ case Tegra::Shader::HalfType::F32:
+ return fmt::format("vec2(fromHalf2({}))", operand);
+ case Tegra::Shader::HalfType::H0_H0:
+ return fmt::format("vec2({}[0])", operand);
+ case Tegra::Shader::HalfType::H1_H1:
+ return fmt::format("vec2({}[1])", operand);
+ }
+ UNREACHABLE();
+ return "0";
+ }();
+ return fmt::format("fromHalf2({})", value);
+ }
+
std::string HMergeF32(Operation operation) {
- return "float(toHalf2(" + Visit(operation[0]) + ")[0])";
+ return fmt::format("float(toHalf2({})[0])", Visit(operation[0]));
}
std::string HMergeH0(Operation operation) {
- return "fromHalf2(vec2(toHalf2(" + Visit(operation[0]) + ")[1], toHalf2(" +
- Visit(operation[1]) + ")[0]))";
+ return fmt::format("fromHalf2(vec2(toHalf2({})[0], toHalf2({})[1]))", Visit(operation[1]),
+ Visit(operation[0]));
}
std::string HMergeH1(Operation operation) {
- return "fromHalf2(vec2(toHalf2(" + Visit(operation[0]) + ")[0], toHalf2(" +
- Visit(operation[1]) + ")[1]))";
+ return fmt::format("fromHalf2(vec2(toHalf2({})[0], toHalf2({})[1]))", Visit(operation[0]),
+ Visit(operation[1]));
}
std::string HPack2(Operation operation) {
- return "utof(packHalf2x16(vec2(" + Visit(operation[0]) + ", " + Visit(operation[1]) + ")))";
+ return fmt::format("utof(packHalf2x16(vec2({}, {})))", Visit(operation[0]),
+ Visit(operation[1]));
}
template <Type type>
@@ -1059,12 +1256,12 @@ private:
}
std::string LogicalAssign(Operation operation) {
- const Node dest = operation[0];
- const Node src = operation[1];
+ const Node& dest = operation[0];
+ const Node& src = operation[1];
std::string target;
- if (const auto pred = std::get_if<PredicateNode>(dest)) {
+ if (const auto pred = std::get_if<PredicateNode>(&*dest)) {
ASSERT_MSG(!pred->IsNegated(), "Negating logical assignment");
const auto index = pred->GetIndex();
@@ -1075,11 +1272,11 @@ private:
return {};
}
target = GetPredicate(index);
- } else if (const auto flag = std::get_if<InternalFlagNode>(dest)) {
+ } else if (const auto flag = std::get_if<InternalFlagNode>(&*dest)) {
target = GetInternalFlag(flag->GetFlag());
}
- code.AddLine(target + " = " + Visit(src) + ';');
+ code.AddLine("{} = {};", target, Visit(src));
return {};
}
@@ -1101,7 +1298,7 @@ private:
std::string LogicalPick2(Operation operation) {
const std::string pair = VisitOperand(operation, 0, Type::Bool2);
- return pair + '[' + VisitOperand(operation, 1, Type::Uint) + ']';
+ return fmt::format("{}[{}]", pair, VisitOperand(operation, 1, Type::Uint));
}
std::string LogicalAll2(Operation operation) {
@@ -1112,41 +1309,54 @@ private:
return GenerateUnary(operation, "any", Type::Bool, Type::Bool2);
}
+ template <bool with_nan>
+ std::string GenerateHalfComparison(Operation operation, const std::string& compare_op) {
+ const std::string comparison{GenerateBinaryCall(operation, compare_op, Type::Bool2,
+ Type::HalfFloat, Type::HalfFloat)};
+ if constexpr (!with_nan) {
+ return comparison;
+ }
+ return fmt::format("halfFloatNanComparison({}, {}, {})", comparison,
+ VisitOperand(operation, 0, Type::HalfFloat),
+ VisitOperand(operation, 1, Type::HalfFloat));
+ }
+
+ template <bool with_nan>
std::string Logical2HLessThan(Operation operation) {
- return GenerateBinaryCall(operation, "lessThan", Type::Bool2, Type::HalfFloat,
- Type::HalfFloat);
+ return GenerateHalfComparison<with_nan>(operation, "lessThan");
}
+ template <bool with_nan>
std::string Logical2HEqual(Operation operation) {
- return GenerateBinaryCall(operation, "equal", Type::Bool2, Type::HalfFloat,
- Type::HalfFloat);
+ return GenerateHalfComparison<with_nan>(operation, "equal");
}
+ template <bool with_nan>
std::string Logical2HLessEqual(Operation operation) {
- return GenerateBinaryCall(operation, "lessThanEqual", Type::Bool2, Type::HalfFloat,
- Type::HalfFloat);
+ return GenerateHalfComparison<with_nan>(operation, "lessThanEqual");
}
+ template <bool with_nan>
std::string Logical2HGreaterThan(Operation operation) {
- return GenerateBinaryCall(operation, "greaterThan", Type::Bool2, Type::HalfFloat,
- Type::HalfFloat);
+ return GenerateHalfComparison<with_nan>(operation, "greaterThan");
}
+ template <bool with_nan>
std::string Logical2HNotEqual(Operation operation) {
- return GenerateBinaryCall(operation, "notEqual", Type::Bool2, Type::HalfFloat,
- Type::HalfFloat);
+ return GenerateHalfComparison<with_nan>(operation, "notEqual");
}
+ template <bool with_nan>
std::string Logical2HGreaterEqual(Operation operation) {
- return GenerateBinaryCall(operation, "greaterThanEqual", Type::Bool2, Type::HalfFloat,
- Type::HalfFloat);
+ return GenerateHalfComparison<with_nan>(operation, "greaterThanEqual");
}
std::string Texture(Operation operation) {
const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
ASSERT(meta);
- std::string expr = GenerateTexture(operation, "texture");
+ std::string expr = GenerateTexture(
+ operation, "", {TextureAoffi{}, TextureArgument{Type::Float, meta->bias}});
if (meta->sampler.IsShadow()) {
expr = "vec4(" + expr + ')';
}
@@ -1157,7 +1367,8 @@ private:
const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
ASSERT(meta);
- std::string expr = GenerateTexture(operation, "textureLod");
+ std::string expr = GenerateTexture(
+ operation, "Lod", {TextureArgument{Type::Float, meta->lod}, TextureAoffi{}});
if (meta->sampler.IsShadow()) {
expr = "vec4(" + expr + ')';
}
@@ -1168,7 +1379,9 @@ private:
const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
ASSERT(meta);
- return GenerateTexture(operation, "textureGather", !meta->sampler.IsShadow()) +
+ const auto type = meta->sampler.IsShadow() ? Type::Float : Type::Int;
+ return GenerateTexture(operation, "Gather",
+ {TextureArgument{type, meta->component}, TextureAoffi{}}) +
GetSwizzle(meta->element);
}
@@ -1182,11 +1395,12 @@ private:
switch (meta->element) {
case 0:
case 1:
- return "textureSize(" + sampler + ", " + lod + ')' + GetSwizzle(meta->element);
+ return fmt::format("itof(int(textureSize({}, {}){}))", sampler, lod,
+ GetSwizzle(meta->element));
case 2:
return "0";
case 3:
- return "textureQueryLevels(" + sampler + ')';
+ return fmt::format("itof(textureQueryLevels({}))", sampler);
}
UNREACHABLE();
return "0";
@@ -1197,8 +1411,9 @@ private:
ASSERT(meta);
if (meta->element < 2) {
- return "itof(int((" + GenerateTexture(operation, "textureQueryLod") + " * vec2(256))" +
- GetSwizzle(meta->element) + "))";
+ return fmt::format("itof(int(({} * vec2(256)){}))",
+ GenerateTexture(operation, "QueryLod", {}),
+ GetSwizzle(meta->element));
}
return "0";
}
@@ -1224,9 +1439,9 @@ private:
else if (next < count)
expr += ", ";
}
- for (std::size_t i = 0; i < meta->extras.size(); ++i) {
+ if (meta->lod) {
expr += ", ";
- expr += CastOperand(Visit(meta->extras.at(i)), Type::Int);
+ expr += CastOperand(Visit(meta->lod), Type::Int);
}
expr += ')';
@@ -1234,24 +1449,27 @@ private:
}
std::string Branch(Operation operation) {
- const auto target = std::get_if<ImmediateNode>(operation[0]);
+ const auto target = std::get_if<ImmediateNode>(&*operation[0]);
UNIMPLEMENTED_IF(!target);
- code.AddLine(fmt::format("jmp_to = 0x{:x}u;", target->GetValue()));
+ code.AddLine("jmp_to = 0x{:x}u;", target->GetValue());
code.AddLine("break;");
return {};
}
std::string PushFlowStack(Operation operation) {
- const auto target = std::get_if<ImmediateNode>(operation[0]);
+ const auto stack = std::get<MetaStackClass>(operation.GetMeta());
+ const auto target = std::get_if<ImmediateNode>(&*operation[0]);
UNIMPLEMENTED_IF(!target);
- code.AddLine(fmt::format("flow_stack[flow_stack_top++] = 0x{:x}u;", target->GetValue()));
+ code.AddLine("{}[{}++] = 0x{:x}u;", FlowStackName(stack), FlowStackTopName(stack),
+ target->GetValue());
return {};
}
std::string PopFlowStack(Operation operation) {
- code.AddLine("jmp_to = flow_stack[--flow_stack_top];");
+ const auto stack = std::get<MetaStackClass>(operation.GetMeta());
+ code.AddLine("jmp_to = {}[--{}];", FlowStackName(stack), FlowStackTopName(stack));
code.AddLine("break;");
return {};
}
@@ -1272,34 +1490,15 @@ private:
UNIMPLEMENTED_IF_MSG(header.ps.omap.sample_mask != 0, "Sample mask write is unimplemented");
- code.AddLine("if (alpha_test[0] != 0) {");
- ++code.scope;
- // We start on the register containing the alpha value in the first RT.
- u32 current_reg = 3;
- for (u32 render_target = 0; render_target < Maxwell::NumRenderTargets; ++render_target) {
- // TODO(Blinkhawk): verify the behavior of alpha testing on hardware when
- // multiple render targets are used.
- if (header.ps.IsColorComponentOutputEnabled(render_target, 0) ||
- header.ps.IsColorComponentOutputEnabled(render_target, 1) ||
- header.ps.IsColorComponentOutputEnabled(render_target, 2) ||
- header.ps.IsColorComponentOutputEnabled(render_target, 3)) {
- code.AddLine(
- fmt::format("if (!AlphaFunc({})) discard;", SafeGetRegister(current_reg)));
- current_reg += 4;
- }
- }
- --code.scope;
- code.AddLine('}');
-
// Write the color outputs using the data in the shader registers, disabled
// rendertargets/components are skipped in the register assignment.
- current_reg = 0;
+ u32 current_reg = 0;
for (u32 render_target = 0; render_target < Maxwell::NumRenderTargets; ++render_target) {
// TODO(Subv): Figure out how dual-source blending is configured in the Switch.
for (u32 component = 0; component < 4; ++component) {
if (header.ps.IsColorComponentOutputEnabled(render_target, component)) {
- code.AddLine(fmt::format("FragColor{}[{}] = {};", render_target, component,
- SafeGetRegister(current_reg)));
+ code.AddLine("FragColor{}[{}] = {};", render_target, component,
+ SafeGetRegister(current_reg));
++current_reg;
}
}
@@ -1308,7 +1507,7 @@ 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.
- code.AddLine("gl_FragDepth = " + SafeGetRegister(current_reg + 1) + ';');
+ code.AddLine("gl_FragDepth = {};", SafeGetRegister(current_reg + 1));
}
code.AddLine("return;");
@@ -1318,11 +1517,11 @@ private:
std::string Discard(Operation operation) {
// Enclose "discard" in a conditional, so that GLSL compilation does not complain
// about unexecuted instructions that may follow this.
- code.AddLine("if (true) {");
+ code.AddLine("if (true) {{");
++code.scope;
code.AddLine("discard;");
--code.scope;
- code.AddLine("}");
+ code.AddLine("}}");
return {};
}
@@ -1332,9 +1531,7 @@ private:
// If a geometry shader is attached, it will always flip (it's the last stage before
// fragment). For more info about flipping, refer to gl_shader_gen.cpp.
- code.AddLine("position.xy *= viewport_flip.xy;");
- code.AddLine("gl_Position = position;");
- code.AddLine("position.w = 1.0;");
+ code.AddLine("gl_Position.xy *= viewport_flip.xy;");
code.AddLine("EmitVertex();");
return {};
}
@@ -1352,6 +1549,16 @@ private:
return "uintBitsToFloat(config_pack[2])";
}
+ template <u32 element>
+ std::string LocalInvocationId(Operation) {
+ return "utof(gl_LocalInvocationID"s + GetSwizzle(element) + ')';
+ }
+
+ template <u32 element>
+ std::string WorkGroupId(Operation) {
+ return "utof(gl_WorkGroupID"s + GetSwizzle(element) + ')';
+ }
+
static constexpr OperationDecompilersArray operation_decompilers = {
&GLSLDecompiler::Assign,
@@ -1423,6 +1630,8 @@ private:
&GLSLDecompiler::Fma<Type::HalfFloat>,
&GLSLDecompiler::Absolute<Type::HalfFloat>,
&GLSLDecompiler::HNegate,
+ &GLSLDecompiler::HClamp,
+ &GLSLDecompiler::HUnpack,
&GLSLDecompiler::HMergeF32,
&GLSLDecompiler::HMergeH0,
&GLSLDecompiler::HMergeH1,
@@ -1459,12 +1668,18 @@ private:
&GLSLDecompiler::LogicalNotEqual<Type::Uint>,
&GLSLDecompiler::LogicalGreaterEqual<Type::Uint>,
- &GLSLDecompiler::Logical2HLessThan,
- &GLSLDecompiler::Logical2HEqual,
- &GLSLDecompiler::Logical2HLessEqual,
- &GLSLDecompiler::Logical2HGreaterThan,
- &GLSLDecompiler::Logical2HNotEqual,
- &GLSLDecompiler::Logical2HGreaterEqual,
+ &GLSLDecompiler::Logical2HLessThan<false>,
+ &GLSLDecompiler::Logical2HEqual<false>,
+ &GLSLDecompiler::Logical2HLessEqual<false>,
+ &GLSLDecompiler::Logical2HGreaterThan<false>,
+ &GLSLDecompiler::Logical2HNotEqual<false>,
+ &GLSLDecompiler::Logical2HGreaterEqual<false>,
+ &GLSLDecompiler::Logical2HLessThan<true>,
+ &GLSLDecompiler::Logical2HEqual<true>,
+ &GLSLDecompiler::Logical2HLessEqual<true>,
+ &GLSLDecompiler::Logical2HGreaterThan<true>,
+ &GLSLDecompiler::Logical2HNotEqual<true>,
+ &GLSLDecompiler::Logical2HGreaterEqual<true>,
&GLSLDecompiler::Texture,
&GLSLDecompiler::TextureLod,
@@ -1483,6 +1698,12 @@ private:
&GLSLDecompiler::EndPrimitive,
&GLSLDecompiler::YNegate,
+ &GLSLDecompiler::LocalInvocationId<0>,
+ &GLSLDecompiler::LocalInvocationId<1>,
+ &GLSLDecompiler::LocalInvocationId<2>,
+ &GLSLDecompiler::WorkGroupId<0>,
+ &GLSLDecompiler::WorkGroupId<1>,
+ &GLSLDecompiler::WorkGroupId<2>,
};
std::string GetRegister(u32 index) const {
@@ -1494,15 +1715,11 @@ private:
}
std::string GetInputAttribute(Attribute::Index attribute) const {
- const auto index{static_cast<u32>(attribute) -
- static_cast<u32>(Attribute::Index::Attribute_0)};
- return GetDeclarationWithSuffix(index, "input_attr");
+ return GetDeclarationWithSuffix(GetGenericAttributeIndex(attribute), "input_attr");
}
std::string GetOutputAttribute(Attribute::Index attribute) const {
- const auto index{static_cast<u32>(attribute) -
- static_cast<u32>(Attribute::Index::Attribute_0)};
- return GetDeclarationWithSuffix(index, "output_attr");
+ return GetDeclarationWithSuffix(GetGenericAttributeIndex(attribute), "output_attr");
}
std::string GetConstBuffer(u32 index) const {
@@ -1532,7 +1749,7 @@ private:
const auto index = static_cast<u32>(flag);
ASSERT(index < static_cast<u32>(InternalFlag::Amount));
- return std::string(InternalFlagNames[index]) + '_' + suffix;
+ return fmt::format("{}_{}", InternalFlagNames[index], suffix);
}
std::string GetSampler(const Sampler& sampler) const {
@@ -1540,9 +1757,22 @@ private:
}
std::string GetDeclarationWithSuffix(u32 index, const std::string& name) const {
- return name + '_' + std::to_string(index) + '_' + suffix;
+ return fmt::format("{}_{}_{}", name, index, suffix);
}
+ u32 GetNumPhysicalInputAttributes() const {
+ return stage == ShaderStage::Vertex ? GetNumPhysicalAttributes() : GetNumPhysicalVaryings();
+ }
+
+ u32 GetNumPhysicalAttributes() const {
+ return std::min<u32>(device.GetMaxVertexAttributes(), Maxwell::NumVertexAttributes);
+ }
+
+ u32 GetNumPhysicalVaryings() const {
+ return std::min<u32>(device.GetMaxVaryings(), Maxwell::NumVaryings);
+ }
+
+ const Device& device;
const ShaderIR& ir;
const ShaderStage stage;
const std::string suffix;
@@ -1551,25 +1781,33 @@ private:
ShaderWriter code;
};
+} // Anonymous namespace
+
std::string GetCommonDeclarations() {
- const auto cbuf = std::to_string(MAX_CONSTBUFFER_ELEMENTS);
- const auto gmem = std::to_string(MAX_GLOBALMEMORY_ELEMENTS);
- return "#define MAX_CONSTBUFFER_ELEMENTS " + cbuf + "\n" +
- "#define MAX_GLOBALMEMORY_ELEMENTS " + gmem + "\n" +
- "#define ftoi floatBitsToInt\n"
- "#define ftou floatBitsToUint\n"
- "#define itof intBitsToFloat\n"
- "#define utof uintBitsToFloat\n\n"
- "float fromHalf2(vec2 pair) {\n"
- " return utof(packHalf2x16(pair));\n"
- "}\n\n"
- "vec2 toHalf2(float value) {\n"
- " return unpackHalf2x16(ftou(value));\n"
- "}\n";
+ return fmt::format(
+ "#define MAX_CONSTBUFFER_ELEMENTS {}\n"
+ "#define ftoi floatBitsToInt\n"
+ "#define ftou floatBitsToUint\n"
+ "#define itof intBitsToFloat\n"
+ "#define utof uintBitsToFloat\n\n"
+ "float fromHalf2(vec2 pair) {{\n"
+ " return utof(packHalf2x16(pair));\n"
+ "}}\n\n"
+ "vec2 toHalf2(float value) {{\n"
+ " return unpackHalf2x16(ftou(value));\n"
+ "}}\n\n"
+ "bvec2 halfFloatNanComparison(bvec2 comparison, vec2 pair1, vec2 pair2) {{\n"
+ " bvec2 is_nan1 = isnan(pair1);\n"
+ " bvec2 is_nan2 = isnan(pair2);\n"
+ " return bvec2(comparison.x || is_nan1.x || is_nan2.x, comparison.y || is_nan1.y || "
+ "is_nan2.y);\n"
+ "}}\n",
+ MAX_CONSTBUFFER_ELEMENTS);
}
-ProgramResult Decompile(const ShaderIR& ir, Maxwell::ShaderStage stage, const std::string& suffix) {
- GLSLDecompiler decompiler(ir, stage, suffix);
+ProgramResult Decompile(const Device& device, const ShaderIR& ir, Maxwell::ShaderStage stage,
+ const std::string& suffix) {
+ GLSLDecompiler decompiler(device, ir, stage, suffix);
decompiler.Decompile();
return {decompiler.GetResult(), decompiler.GetShaderEntries()};
}
diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.h b/src/video_core/renderer_opengl/gl_shader_decompiler.h
index 72aca4938..c1569e737 100644
--- a/src/video_core/renderer_opengl/gl_shader_decompiler.h
+++ b/src/video_core/renderer_opengl/gl_shader_decompiler.h
@@ -5,7 +5,6 @@
#pragma once
#include <array>
-#include <set>
#include <string>
#include <utility>
#include <vector>
@@ -13,6 +12,10 @@
#include "video_core/engines/maxwell_3d.h"
#include "video_core/shader/shader_ir.h"
+namespace OpenGL {
+class Device;
+}
+
namespace VideoCommon::Shader {
class ShaderIR;
}
@@ -40,8 +43,9 @@ private:
class GlobalMemoryEntry {
public:
- explicit GlobalMemoryEntry(u32 cbuf_index, u32 cbuf_offset)
- : cbuf_index{cbuf_index}, cbuf_offset{cbuf_offset} {}
+ 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 GetCbufIndex() const {
return cbuf_index;
@@ -51,14 +55,25 @@ public:
return cbuf_offset;
}
+ bool IsRead() const {
+ return is_read;
+ }
+
+ bool IsWritten() const {
+ return is_written;
+ }
+
private:
u32 cbuf_index{};
u32 cbuf_offset{};
+ bool is_read{};
+ bool is_written{};
};
struct ShaderEntries {
std::vector<ConstBufferEntry> const_buffers;
std::vector<SamplerEntry> samplers;
+ std::vector<SamplerEntry> bindless_samplers;
std::vector<GlobalMemoryEntry> global_memory_entries;
std::array<bool, Maxwell::NumClipDistances> clip_distances{};
std::size_t shader_length{};
@@ -66,7 +81,7 @@ struct ShaderEntries {
std::string GetCommonDeclarations();
-ProgramResult Decompile(const VideoCommon::Shader::ShaderIR& ir, Maxwell::ShaderStage stage,
- const std::string& suffix);
+ProgramResult Decompile(const Device& device, const VideoCommon::Shader::ShaderIR& ir,
+ Maxwell::ShaderStage stage, const std::string& suffix);
-} // namespace OpenGL::GLShader \ No newline at end of file
+} // namespace OpenGL::GLShader
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 82fc4d44b..ee4a45ca2 100644
--- a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp
@@ -4,7 +4,6 @@
#include <cstring>
#include <fmt/format.h>
-#include <lz4.h>
#include "common/assert.h"
#include "common/common_paths.h"
@@ -12,6 +11,7 @@
#include "common/file_util.h"
#include "common/logging/log.h"
#include "common/scm_rev.h"
+#include "common/zstd_compression.h"
#include "core/core.h"
#include "core/hle/kernel/process.h"
@@ -49,39 +49,6 @@ ShaderCacheVersionHash GetShaderCacheVersionHash() {
return hash;
}
-template <typename T>
-std::vector<u8> CompressData(const T* source, std::size_t source_size) {
- if (source_size > LZ4_MAX_INPUT_SIZE) {
- // Source size exceeds LZ4 maximum input size
- return {};
- }
- const auto source_size_int = static_cast<int>(source_size);
- const int max_compressed_size = LZ4_compressBound(source_size_int);
- std::vector<u8> compressed(max_compressed_size);
- const int compressed_size = LZ4_compress_default(reinterpret_cast<const char*>(source),
- reinterpret_cast<char*>(compressed.data()),
- source_size_int, max_compressed_size);
- if (compressed_size <= 0) {
- // Compression failed
- return {};
- }
- compressed.resize(compressed_size);
- return compressed;
-}
-
-std::vector<u8> DecompressData(const std::vector<u8>& compressed, std::size_t uncompressed_size) {
- std::vector<u8> uncompressed(uncompressed_size);
- const int size_check = LZ4_decompress_safe(reinterpret_cast<const char*>(compressed.data()),
- reinterpret_cast<char*>(uncompressed.data()),
- static_cast<int>(compressed.size()),
- static_cast<int>(uncompressed.size()));
- if (static_cast<int>(uncompressed_size) != size_check) {
- // Decompression failed
- return {};
- }
- return uncompressed;
-}
-
} // namespace
ShaderDiskCacheRaw::ShaderDiskCacheRaw(u64 unique_identifier, Maxwell::ShaderProgram program_type,
@@ -139,6 +106,8 @@ bool ShaderDiskCacheRaw::Save(FileUtil::IOFile& file) const {
ShaderDiskCacheOpenGL::ShaderDiskCacheOpenGL(Core::System& system) : system{system} {}
+ShaderDiskCacheOpenGL::~ShaderDiskCacheOpenGL() = default;
+
std::optional<std::pair<std::vector<ShaderDiskCacheRaw>, std::vector<ShaderDiskCacheUsage>>>
ShaderDiskCacheOpenGL::LoadTransferable() {
// Skip games without title id
@@ -210,11 +179,11 @@ ShaderDiskCacheOpenGL::LoadTransferable() {
return {};
}
}
+
return {{raws, usages}};
}
-std::pair<std::unordered_map<u64, ShaderDiskCacheDecompiled>,
- std::unordered_map<ShaderDiskCacheUsage, ShaderDiskCacheDump>>
+std::pair<std::unordered_map<u64, ShaderDiskCacheDecompiled>, ShaderDumpsMap>
ShaderDiskCacheOpenGL::LoadPrecompiled() {
if (!IsUsable())
return {};
@@ -238,62 +207,66 @@ ShaderDiskCacheOpenGL::LoadPrecompiled() {
return *result;
}
-std::optional<std::pair<std::unordered_map<u64, ShaderDiskCacheDecompiled>,
- std::unordered_map<ShaderDiskCacheUsage, ShaderDiskCacheDump>>>
+std::optional<std::pair<std::unordered_map<u64, ShaderDiskCacheDecompiled>, ShaderDumpsMap>>
ShaderDiskCacheOpenGL::LoadPrecompiledFile(FileUtil::IOFile& file) {
+ // Read compressed file from disk and decompress to virtual precompiled cache file
+ std::vector<u8> compressed(file.GetSize());
+ file.ReadBytes(compressed.data(), compressed.size());
+ const std::vector<u8> decompressed = Common::Compression::DecompressDataZSTD(compressed);
+ SaveArrayToPrecompiled(decompressed.data(), decompressed.size());
+ precompiled_cache_virtual_file_offset = 0;
+
ShaderCacheVersionHash file_hash{};
- if (file.ReadArray(file_hash.data(), file_hash.size()) != file_hash.size()) {
+ if (!LoadArrayFromPrecompiled(file_hash.data(), file_hash.size())) {
+ precompiled_cache_virtual_file_offset = 0;
return {};
}
if (GetShaderCacheVersionHash() != file_hash) {
LOG_INFO(Render_OpenGL, "Precompiled cache is from another version of the emulator");
+ precompiled_cache_virtual_file_offset = 0;
return {};
}
std::unordered_map<u64, ShaderDiskCacheDecompiled> decompiled;
- std::unordered_map<ShaderDiskCacheUsage, ShaderDiskCacheDump> dumps;
- while (file.Tell() < file.GetSize()) {
+ ShaderDumpsMap dumps;
+ while (precompiled_cache_virtual_file_offset < precompiled_cache_virtual_file.GetSize()) {
PrecompiledEntryKind kind{};
- if (file.ReadBytes(&kind, sizeof(u32)) != sizeof(u32)) {
+ if (!LoadObjectFromPrecompiled(kind)) {
return {};
}
switch (kind) {
case PrecompiledEntryKind::Decompiled: {
u64 unique_identifier{};
- if (file.ReadBytes(&unique_identifier, sizeof(u64)) != sizeof(u64))
+ if (!LoadObjectFromPrecompiled(unique_identifier)) {
return {};
+ }
- const auto entry = LoadDecompiledEntry(file);
- if (!entry)
+ auto entry = LoadDecompiledEntry();
+ if (!entry) {
return {};
+ }
decompiled.insert({unique_identifier, std::move(*entry)});
break;
}
case PrecompiledEntryKind::Dump: {
ShaderDiskCacheUsage usage;
- if (file.ReadBytes(&usage, sizeof(usage)) != sizeof(usage))
+ if (!LoadObjectFromPrecompiled(usage)) {
return {};
+ }
ShaderDiskCacheDump dump;
- if (file.ReadBytes(&dump.binary_format, sizeof(u32)) != sizeof(u32))
- return {};
-
- u32 binary_length{};
- u32 compressed_size{};
- if (file.ReadBytes(&binary_length, sizeof(u32)) != sizeof(u32) ||
- file.ReadBytes(&compressed_size, sizeof(u32)) != sizeof(u32)) {
+ if (!LoadObjectFromPrecompiled(dump.binary_format)) {
return {};
}
- std::vector<u8> compressed_binary(compressed_size);
- if (file.ReadArray(compressed_binary.data(), compressed_binary.size()) !=
- compressed_binary.size()) {
+ u32 binary_length{};
+ if (!LoadObjectFromPrecompiled(binary_length)) {
return {};
}
- dump.binary = DecompressData(compressed_binary, binary_length);
- if (dump.binary.empty()) {
+ dump.binary.resize(binary_length);
+ if (!LoadArrayFromPrecompiled(dump.binary.data(), dump.binary.size())) {
return {};
}
@@ -307,143 +280,151 @@ ShaderDiskCacheOpenGL::LoadPrecompiledFile(FileUtil::IOFile& file) {
return {{decompiled, dumps}};
}
-std::optional<ShaderDiskCacheDecompiled> ShaderDiskCacheOpenGL::LoadDecompiledEntry(
- FileUtil::IOFile& file) {
+std::optional<ShaderDiskCacheDecompiled> ShaderDiskCacheOpenGL::LoadDecompiledEntry() {
u32 code_size{};
- u32 compressed_code_size{};
- if (file.ReadBytes(&code_size, sizeof(u32)) != sizeof(u32) ||
- file.ReadBytes(&compressed_code_size, sizeof(u32)) != sizeof(u32)) {
+ if (!LoadObjectFromPrecompiled(code_size)) {
return {};
}
- std::vector<u8> compressed_code(compressed_code_size);
- if (file.ReadArray(compressed_code.data(), compressed_code.size()) != compressed_code.size()) {
+ std::string code(code_size, '\0');
+ if (!LoadArrayFromPrecompiled(code.data(), code.size())) {
return {};
}
- const std::vector<u8> code = DecompressData(compressed_code, code_size);
- if (code.empty()) {
- return {};
- }
ShaderDiskCacheDecompiled entry;
- entry.code = std::string(reinterpret_cast<const char*>(code.data()), code_size);
+ entry.code = std::move(code);
u32 const_buffers_count{};
- if (file.ReadBytes(&const_buffers_count, sizeof(u32)) != sizeof(u32))
+ if (!LoadObjectFromPrecompiled(const_buffers_count)) {
return {};
+ }
+
for (u32 i = 0; i < const_buffers_count; ++i) {
u32 max_offset{};
u32 index{};
- u8 is_indirect{};
- if (file.ReadBytes(&max_offset, sizeof(u32)) != sizeof(u32) ||
- file.ReadBytes(&index, sizeof(u32)) != sizeof(u32) ||
- file.ReadBytes(&is_indirect, sizeof(u8)) != sizeof(u8)) {
+ bool is_indirect{};
+ if (!LoadObjectFromPrecompiled(max_offset) || !LoadObjectFromPrecompiled(index) ||
+ !LoadObjectFromPrecompiled(is_indirect)) {
return {};
}
- entry.entries.const_buffers.emplace_back(max_offset, is_indirect != 0, index);
+ entry.entries.const_buffers.emplace_back(max_offset, is_indirect, index);
}
u32 samplers_count{};
- if (file.ReadBytes(&samplers_count, sizeof(u32)) != sizeof(u32))
+ if (!LoadObjectFromPrecompiled(samplers_count)) {
return {};
+ }
+
for (u32 i = 0; i < samplers_count; ++i) {
u64 offset{};
u64 index{};
u32 type{};
- u8 is_array{};
- u8 is_shadow{};
- if (file.ReadBytes(&offset, sizeof(u64)) != sizeof(u64) ||
- file.ReadBytes(&index, sizeof(u64)) != sizeof(u64) ||
- file.ReadBytes(&type, sizeof(u32)) != sizeof(u32) ||
- file.ReadBytes(&is_array, sizeof(u8)) != sizeof(u8) ||
- file.ReadBytes(&is_shadow, sizeof(u8)) != sizeof(u8)) {
+ bool is_array{};
+ bool is_shadow{};
+ bool is_bindless{};
+ if (!LoadObjectFromPrecompiled(offset) || !LoadObjectFromPrecompiled(index) ||
+ !LoadObjectFromPrecompiled(type) || !LoadObjectFromPrecompiled(is_array) ||
+ !LoadObjectFromPrecompiled(is_shadow) || !LoadObjectFromPrecompiled(is_bindless)) {
return {};
}
entry.entries.samplers.emplace_back(
static_cast<std::size_t>(offset), static_cast<std::size_t>(index),
- static_cast<Tegra::Shader::TextureType>(type), is_array != 0, is_shadow != 0);
+ static_cast<Tegra::Shader::TextureType>(type), is_array, is_shadow, is_bindless);
}
u32 global_memory_count{};
- if (file.ReadBytes(&global_memory_count, sizeof(u32)) != sizeof(u32))
+ if (!LoadObjectFromPrecompiled(global_memory_count)) {
return {};
+ }
+
for (u32 i = 0; i < global_memory_count; ++i) {
u32 cbuf_index{};
u32 cbuf_offset{};
- if (file.ReadBytes(&cbuf_index, sizeof(u32)) != sizeof(u32) ||
- file.ReadBytes(&cbuf_offset, sizeof(u32)) != sizeof(u32)) {
+ bool is_read{};
+ bool is_written{};
+ if (!LoadObjectFromPrecompiled(cbuf_index) || !LoadObjectFromPrecompiled(cbuf_offset) ||
+ !LoadObjectFromPrecompiled(is_read) || !LoadObjectFromPrecompiled(is_written)) {
return {};
}
- entry.entries.global_memory_entries.emplace_back(cbuf_index, cbuf_offset);
+ entry.entries.global_memory_entries.emplace_back(cbuf_index, cbuf_offset, is_read,
+ is_written);
}
for (auto& clip_distance : entry.entries.clip_distances) {
- u8 clip_distance_raw{};
- if (file.ReadBytes(&clip_distance_raw, sizeof(u8)) != sizeof(u8))
+ if (!LoadObjectFromPrecompiled(clip_distance)) {
return {};
- clip_distance = clip_distance_raw != 0;
+ }
}
u64 shader_length{};
- if (file.ReadBytes(&shader_length, sizeof(u64)) != sizeof(u64))
+ if (!LoadObjectFromPrecompiled(shader_length)) {
return {};
+ }
+
entry.entries.shader_length = static_cast<std::size_t>(shader_length);
return entry;
}
-bool ShaderDiskCacheOpenGL::SaveDecompiledFile(FileUtil::IOFile& file, u64 unique_identifier,
- const std::string& code,
- const std::vector<u8>& compressed_code,
+bool ShaderDiskCacheOpenGL::SaveDecompiledFile(u64 unique_identifier, const std::string& code,
const GLShader::ShaderEntries& entries) {
- if (file.WriteObject(static_cast<u32>(PrecompiledEntryKind::Decompiled)) != 1 ||
- file.WriteObject(unique_identifier) != 1 ||
- file.WriteObject(static_cast<u32>(code.size())) != 1 ||
- file.WriteObject(static_cast<u32>(compressed_code.size())) != 1 ||
- file.WriteArray(compressed_code.data(), compressed_code.size()) != compressed_code.size()) {
+ if (!SaveObjectToPrecompiled(static_cast<u32>(PrecompiledEntryKind::Decompiled)) ||
+ !SaveObjectToPrecompiled(unique_identifier) ||
+ !SaveObjectToPrecompiled(static_cast<u32>(code.size())) ||
+ !SaveArrayToPrecompiled(code.data(), code.size())) {
return false;
}
- if (file.WriteObject(static_cast<u32>(entries.const_buffers.size())) != 1)
+ if (!SaveObjectToPrecompiled(static_cast<u32>(entries.const_buffers.size()))) {
return false;
+ }
for (const auto& cbuf : entries.const_buffers) {
- if (file.WriteObject(static_cast<u32>(cbuf.GetMaxOffset())) != 1 ||
- file.WriteObject(static_cast<u32>(cbuf.GetIndex())) != 1 ||
- file.WriteObject(static_cast<u8>(cbuf.IsIndirect() ? 1 : 0)) != 1) {
+ if (!SaveObjectToPrecompiled(static_cast<u32>(cbuf.GetMaxOffset())) ||
+ !SaveObjectToPrecompiled(static_cast<u32>(cbuf.GetIndex())) ||
+ !SaveObjectToPrecompiled(cbuf.IsIndirect())) {
return false;
}
}
- if (file.WriteObject(static_cast<u32>(entries.samplers.size())) != 1)
+ if (!SaveObjectToPrecompiled(static_cast<u32>(entries.samplers.size()))) {
return false;
+ }
for (const auto& sampler : entries.samplers) {
- if (file.WriteObject(static_cast<u64>(sampler.GetOffset())) != 1 ||
- file.WriteObject(static_cast<u64>(sampler.GetIndex())) != 1 ||
- file.WriteObject(static_cast<u32>(sampler.GetType())) != 1 ||
- file.WriteObject(static_cast<u8>(sampler.IsArray() ? 1 : 0)) != 1 ||
- file.WriteObject(static_cast<u8>(sampler.IsShadow() ? 1 : 0)) != 1) {
+ if (!SaveObjectToPrecompiled(static_cast<u64>(sampler.GetOffset())) ||
+ !SaveObjectToPrecompiled(static_cast<u64>(sampler.GetIndex())) ||
+ !SaveObjectToPrecompiled(static_cast<u32>(sampler.GetType())) ||
+ !SaveObjectToPrecompiled(sampler.IsArray()) ||
+ !SaveObjectToPrecompiled(sampler.IsShadow()) ||
+ !SaveObjectToPrecompiled(sampler.IsBindless())) {
return false;
}
}
- if (file.WriteObject(static_cast<u32>(entries.global_memory_entries.size())) != 1)
+ if (!SaveObjectToPrecompiled(static_cast<u32>(entries.global_memory_entries.size()))) {
return false;
+ }
for (const auto& gmem : entries.global_memory_entries) {
- if (file.WriteObject(static_cast<u32>(gmem.GetCbufIndex())) != 1 ||
- file.WriteObject(static_cast<u32>(gmem.GetCbufOffset())) != 1) {
+ if (!SaveObjectToPrecompiled(static_cast<u32>(gmem.GetCbufIndex())) ||
+ !SaveObjectToPrecompiled(static_cast<u32>(gmem.GetCbufOffset())) ||
+ !SaveObjectToPrecompiled(gmem.IsRead()) || !SaveObjectToPrecompiled(gmem.IsWritten())) {
return false;
}
}
for (const bool clip_distance : entries.clip_distances) {
- if (file.WriteObject(static_cast<u8>(clip_distance ? 1 : 0)) != 1)
+ if (!SaveObjectToPrecompiled(clip_distance)) {
return false;
+ }
+ }
+
+ if (!SaveObjectToPrecompiled(static_cast<u64>(entries.shader_length))) {
+ return false;
}
- return file.WriteObject(static_cast<u64>(entries.shader_length)) == 1;
+ return true;
}
-void ShaderDiskCacheOpenGL::InvalidateTransferable() const {
+void ShaderDiskCacheOpenGL::InvalidateTransferable() {
if (!FileUtil::Delete(GetTransferablePath())) {
LOG_ERROR(Render_OpenGL, "Failed to invalidate transferable file={}",
GetTransferablePath());
@@ -451,7 +432,10 @@ void ShaderDiskCacheOpenGL::InvalidateTransferable() const {
InvalidatePrecompiled();
}
-void ShaderDiskCacheOpenGL::InvalidatePrecompiled() const {
+void ShaderDiskCacheOpenGL::InvalidatePrecompiled() {
+ // Clear virtaul precompiled cache file
+ precompiled_cache_virtual_file.Resize(0);
+
if (!FileUtil::Delete(GetPrecompiledPath())) {
LOG_ERROR(Render_OpenGL, "Failed to invalidate precompiled file={}", GetPrecompiledPath());
}
@@ -487,7 +471,10 @@ void ShaderDiskCacheOpenGL::SaveUsage(const ShaderDiskCacheUsage& usage) {
ASSERT_MSG(it != transferable.end(), "Saving shader usage without storing raw previously");
auto& usages{it->second};
- ASSERT(usages.find(usage) == usages.end());
+ if (usages.find(usage) != usages.end()) {
+ // Skip this variant since the shader is already stored.
+ return;
+ }
usages.insert(usage);
FileUtil::IOFile file = AppendTransferableFile();
@@ -507,21 +494,13 @@ void ShaderDiskCacheOpenGL::SaveDecompiled(u64 unique_identifier, const std::str
if (!IsUsable())
return;
- const std::vector<u8> compressed_code{CompressData(code.data(), code.size())};
- if (compressed_code.empty()) {
- LOG_ERROR(Render_OpenGL, "Failed to compress GLSL code - skipping shader {:016x}",
- unique_identifier);
- return;
+ if (precompiled_cache_virtual_file.GetSize() == 0) {
+ SavePrecompiledHeaderToVirtualPrecompiledCache();
}
- FileUtil::IOFile file = AppendPrecompiledFile();
- if (!file.IsOpen())
- return;
-
- if (!SaveDecompiledFile(file, unique_identifier, code, compressed_code, entries)) {
+ if (!SaveDecompiledFile(unique_identifier, code, entries)) {
LOG_ERROR(Render_OpenGL,
"Failed to save decompiled entry to the precompiled file - removing");
- file.Close();
InvalidatePrecompiled();
}
}
@@ -537,26 +516,13 @@ void ShaderDiskCacheOpenGL::SaveDump(const ShaderDiskCacheUsage& usage, GLuint p
std::vector<u8> binary(binary_length);
glGetProgramBinary(program, binary_length, nullptr, &binary_format, binary.data());
- const std::vector<u8> compressed_binary = CompressData(binary.data(), binary.size());
- if (compressed_binary.empty()) {
- LOG_ERROR(Render_OpenGL, "Failed to compress binary program in shader={:016x}",
- usage.unique_identifier);
- return;
- }
-
- FileUtil::IOFile file = AppendPrecompiledFile();
- if (!file.IsOpen())
- return;
-
- if (file.WriteObject(static_cast<u32>(PrecompiledEntryKind::Dump)) != 1 ||
- file.WriteObject(usage) != 1 || file.WriteObject(static_cast<u32>(binary_format)) != 1 ||
- file.WriteObject(static_cast<u32>(binary_length)) != 1 ||
- file.WriteObject(static_cast<u32>(compressed_binary.size())) != 1 ||
- file.WriteArray(compressed_binary.data(), compressed_binary.size()) !=
- compressed_binary.size()) {
+ if (!SaveObjectToPrecompiled(static_cast<u32>(PrecompiledEntryKind::Dump)) ||
+ !SaveObjectToPrecompiled(usage) ||
+ !SaveObjectToPrecompiled(static_cast<u32>(binary_format)) ||
+ !SaveObjectToPrecompiled(static_cast<u32>(binary_length)) ||
+ !SaveArrayToPrecompiled(binary.data(), binary.size())) {
LOG_ERROR(Render_OpenGL, "Failed to save binary program file in shader={:016x} - removing",
usage.unique_identifier);
- file.Close();
InvalidatePrecompiled();
return;
}
@@ -589,28 +555,33 @@ FileUtil::IOFile ShaderDiskCacheOpenGL::AppendTransferableFile() const {
return file;
}
-FileUtil::IOFile ShaderDiskCacheOpenGL::AppendPrecompiledFile() const {
- if (!EnsureDirectories())
- return {};
+void ShaderDiskCacheOpenGL::SavePrecompiledHeaderToVirtualPrecompiledCache() {
+ const auto hash{GetShaderCacheVersionHash()};
+ if (!SaveArrayToPrecompiled(hash.data(), hash.size())) {
+ LOG_ERROR(
+ Render_OpenGL,
+ "Failed to write precompiled cache version hash to virtual precompiled cache file");
+ }
+}
+
+void ShaderDiskCacheOpenGL::SaveVirtualPrecompiledFile() {
+ precompiled_cache_virtual_file_offset = 0;
+ const std::vector<u8>& uncompressed = precompiled_cache_virtual_file.ReadAllBytes();
+ const std::vector<u8>& compressed =
+ Common::Compression::CompressDataZSTDDefault(uncompressed.data(), uncompressed.size());
const auto precompiled_path{GetPrecompiledPath()};
- const bool existed = FileUtil::Exists(precompiled_path);
+ FileUtil::IOFile file(precompiled_path, "wb");
- FileUtil::IOFile file(precompiled_path, "ab");
if (!file.IsOpen()) {
LOG_ERROR(Render_OpenGL, "Failed to open precompiled cache in path={}", precompiled_path);
- return {};
+ return;
}
-
- if (!existed || file.GetSize() == 0) {
- const auto hash{GetShaderCacheVersionHash()};
- if (file.WriteArray(hash.data(), hash.size()) != hash.size()) {
- LOG_ERROR(Render_OpenGL, "Failed to write precompiled cache version hash in path={}",
- precompiled_path);
- return {};
- }
+ if (file.WriteBytes(compressed.data(), compressed.size()) != compressed.size()) {
+ LOG_ERROR(Render_OpenGL, "Failed to write precompiled cache version in path={}",
+ precompiled_path);
+ return;
}
- return file;
}
bool ShaderDiskCacheOpenGL::EnsureDirectories() const {
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 6be0c0547..ecd72ba58 100644
--- a/src/video_core/renderer_opengl/gl_shader_disk_cache.h
+++ b/src/video_core/renderer_opengl/gl_shader_disk_cache.h
@@ -16,6 +16,7 @@
#include "common/assert.h"
#include "common/common_types.h"
+#include "core/file_sys/vfs_vector.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/renderer_opengl/gl_shader_gen.h"
@@ -32,6 +33,11 @@ namespace OpenGL {
using ProgramCode = std::vector<u64>;
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
+struct ShaderDiskCacheUsage;
+struct ShaderDiskCacheDump;
+
+using ShaderDumpsMap = std::unordered_map<ShaderDiskCacheUsage, ShaderDiskCacheDump>;
+
/// Allocated bindings used by an OpenGL shader program
struct BaseBindings {
u32 cbuf{};
@@ -69,14 +75,14 @@ namespace std {
template <>
struct hash<OpenGL::BaseBindings> {
- std::size_t operator()(const OpenGL::BaseBindings& bindings) const {
+ std::size_t operator()(const OpenGL::BaseBindings& bindings) const noexcept {
return bindings.cbuf | bindings.gmem << 8 | bindings.sampler << 16;
}
};
template <>
struct hash<OpenGL::ShaderDiskCacheUsage> {
- std::size_t operator()(const OpenGL::ShaderDiskCacheUsage& usage) const {
+ std::size_t operator()(const OpenGL::ShaderDiskCacheUsage& usage) const noexcept {
return static_cast<std::size_t>(usage.unique_identifier) ^
std::hash<OpenGL::BaseBindings>()(usage.bindings) ^ usage.primitive << 16;
}
@@ -161,6 +167,7 @@ struct ShaderDiskCacheDump {
class ShaderDiskCacheOpenGL {
public:
explicit ShaderDiskCacheOpenGL(Core::System& system);
+ ~ShaderDiskCacheOpenGL();
/// Loads transferable cache. If file has a old version or on failure, it deletes the file.
std::optional<std::pair<std::vector<ShaderDiskCacheRaw>, std::vector<ShaderDiskCacheUsage>>>
@@ -172,10 +179,10 @@ public:
LoadPrecompiled();
/// Removes the transferable (and precompiled) cache file.
- void InvalidateTransferable() const;
+ void InvalidateTransferable();
- /// Removes the precompiled cache file.
- void InvalidatePrecompiled() const;
+ /// Removes the precompiled cache file and clears virtual precompiled cache file.
+ void InvalidatePrecompiled();
/// Saves a raw dump to the transferable file. Checks for collisions.
void SaveRaw(const ShaderDiskCacheRaw& entry);
@@ -190,18 +197,21 @@ public:
/// Saves a dump entry to the precompiled file. Does not check for collisions.
void SaveDump(const ShaderDiskCacheUsage& usage, GLuint program);
+ /// Serializes virtual precompiled shader cache file to real file
+ void SaveVirtualPrecompiledFile();
+
private:
/// Loads the transferable cache. Returns empty on failure.
std::optional<std::pair<std::unordered_map<u64, ShaderDiskCacheDecompiled>,
std::unordered_map<ShaderDiskCacheUsage, ShaderDiskCacheDump>>>
LoadPrecompiledFile(FileUtil::IOFile& file);
- /// Loads a decompiled cache entry from the passed file. Returns empty on failure.
- std::optional<ShaderDiskCacheDecompiled> LoadDecompiledEntry(FileUtil::IOFile& file);
+ /// Loads a decompiled cache entry from m_precompiled_cache_virtual_file. Returns empty on
+ /// failure.
+ std::optional<ShaderDiskCacheDecompiled> LoadDecompiledEntry();
/// Saves a decompiled entry to the passed file. Returns true on success.
- bool SaveDecompiledFile(FileUtil::IOFile& file, u64 unique_identifier, const std::string& code,
- const std::vector<u8>& compressed_code,
+ bool SaveDecompiledFile(u64 unique_identifier, const std::string& code,
const GLShader::ShaderEntries& entries);
/// Returns if the cache can be used
@@ -210,8 +220,8 @@ private:
/// Opens current game's transferable file and write it's header if it doesn't exist
FileUtil::IOFile AppendTransferableFile() const;
- /// Opens current game's precompiled file and write it's header if it doesn't exist
- FileUtil::IOFile AppendPrecompiledFile() const;
+ /// Save precompiled header to precompiled_cache_in_memory
+ void SavePrecompiledHeaderToVirtualPrecompiledCache();
/// Create shader disk cache directories. Returns true on success.
bool EnsureDirectories() const;
@@ -234,12 +244,59 @@ private:
/// Get current game's title id
std::string GetTitleID() const;
- // Copre system
+ template <typename T>
+ bool SaveArrayToPrecompiled(const T* data, std::size_t length) {
+ const std::size_t write_length = precompiled_cache_virtual_file.WriteArray(
+ data, length, precompiled_cache_virtual_file_offset);
+ precompiled_cache_virtual_file_offset += write_length;
+ return write_length == sizeof(T) * length;
+ }
+
+ template <typename T>
+ bool LoadArrayFromPrecompiled(T* data, std::size_t length) {
+ const std::size_t read_length = precompiled_cache_virtual_file.ReadArray(
+ data, length, precompiled_cache_virtual_file_offset);
+ precompiled_cache_virtual_file_offset += read_length;
+ return read_length == sizeof(T) * length;
+ }
+
+ template <typename T>
+ bool SaveObjectToPrecompiled(const T& object) {
+ return SaveArrayToPrecompiled(&object, 1);
+ }
+
+ bool SaveObjectToPrecompiled(bool object) {
+ const auto value = static_cast<u8>(object);
+ return SaveArrayToPrecompiled(&value, 1);
+ }
+
+ template <typename T>
+ bool LoadObjectFromPrecompiled(T& object) {
+ return LoadArrayFromPrecompiled(&object, 1);
+ }
+
+ bool LoadObjectFromPrecompiled(bool& object) {
+ u8 value;
+ const bool read_ok = LoadArrayFromPrecompiled(&value, 1);
+ if (!read_ok) {
+ return false;
+ }
+
+ object = value != 0;
+ return true;
+ }
+
+ // Core system
Core::System& system;
// Stored transferable shaders
std::map<u64, std::unordered_set<ShaderDiskCacheUsage>> transferable;
+ // Stores whole precompiled cache which will be read from/saved to the precompiled cache file
+ FileSys::VectorVfsFile precompiled_cache_virtual_file;
+ // Stores the current offset of the precompiled cache file for IO purposes
+ std::size_t precompiled_cache_virtual_file_offset = 0;
+
// The cache has been loaded at boot
bool tried_to_load{};
};
-} // namespace OpenGL \ No newline at end of file
+} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp
index 7d96649af..9148629ec 100644
--- a/src/video_core/renderer_opengl/gl_shader_gen.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp
@@ -3,7 +3,6 @@
// Refer to the license.txt file included.
#include <fmt/format.h>
-#include "common/assert.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/renderer_opengl/gl_shader_decompiler.h"
#include "video_core/renderer_opengl/gl_shader_gen.h"
@@ -17,39 +16,35 @@ using VideoCommon::Shader::ShaderIR;
static constexpr u32 PROGRAM_OFFSET{10};
-ProgramResult GenerateVertexShader(const ShaderSetup& setup) {
+ProgramResult GenerateVertexShader(const Device& device, const ShaderSetup& setup) {
const std::string id = fmt::format("{:016x}", setup.program.unique_identifier);
- std::string out = "#extension GL_ARB_separate_shader_objects : enable\n\n";
- out += "// Shader Unique Id: VS" + id + "\n\n";
+ std::string out = "// Shader Unique Id: VS" + id + "\n\n";
out += GetCommonDeclarations();
out += R"(
-layout (location = 0) out vec4 position;
-
layout (std140, binding = EMULATION_UBO_BINDING) uniform vs_config {
vec4 viewport_flip;
uvec4 config_pack; // instance_id, flip_stage, y_direction, padding
- uvec4 alpha_test;
};
)";
- ShaderIR program_ir(setup.program.code, PROGRAM_OFFSET);
- ProgramResult program = Decompile(program_ir, Maxwell3D::Regs::ShaderStage::Vertex, "vertex");
+ const ShaderIR program_ir(setup.program.code, PROGRAM_OFFSET);
+ ProgramResult program =
+ Decompile(device, program_ir, Maxwell3D::Regs::ShaderStage::Vertex, "vertex");
out += program.first;
if (setup.IsDualProgram()) {
- ShaderIR program_ir_b(setup.program.code_b, PROGRAM_OFFSET);
+ const ShaderIR program_ir_b(setup.program.code_b, PROGRAM_OFFSET);
ProgramResult program_b =
- Decompile(program_ir_b, Maxwell3D::Regs::ShaderStage::Vertex, "vertex_b");
+ Decompile(device, program_ir_b, Maxwell3D::Regs::ShaderStage::Vertex, "vertex_b");
out += program_b.first;
}
out += R"(
void main() {
- position = vec4(0.0, 0.0, 0.0, 0.0);
execute_vertex();
)";
@@ -58,45 +53,36 @@ void main() {
}
out += R"(
+
+ // Set Position Y direction
+ gl_Position.y *= utof(config_pack[2]);
// Check if the flip stage is VertexB
// Config pack's second value is flip_stage
if (config_pack[1] == 1) {
// Viewport can be flipped, which is unsupported by glViewport
- position.xy *= viewport_flip.xy;
- }
- gl_Position = position;
-
- // TODO(bunnei): This is likely a hack, position.w should be interpolated as 1.0
- // For now, this is here to bring order in lieu of proper emulation
- if (config_pack[1] == 1) {
- position.w = 1.0;
+ gl_Position.xy *= viewport_flip.xy;
}
})";
- return {out, program.second};
+ return {std::move(out), std::move(program.second)};
}
-ProgramResult GenerateGeometryShader(const ShaderSetup& setup) {
+ProgramResult GenerateGeometryShader(const Device& device, const ShaderSetup& setup) {
const std::string id = fmt::format("{:016x}", setup.program.unique_identifier);
- std::string out = "#extension GL_ARB_separate_shader_objects : enable\n\n";
- out += "// Shader Unique Id: GS" + id + "\n\n";
+ std::string out = "// Shader Unique Id: GS" + id + "\n\n";
out += GetCommonDeclarations();
out += R"(
-layout (location = 0) in vec4 gs_position[];
-layout (location = 0) out vec4 position;
-
layout (std140, binding = EMULATION_UBO_BINDING) uniform gs_config {
vec4 viewport_flip;
uvec4 config_pack; // instance_id, flip_stage, y_direction, padding
- uvec4 alpha_test;
};
)";
- ShaderIR program_ir(setup.program.code, PROGRAM_OFFSET);
+ const ShaderIR program_ir(setup.program.code, PROGRAM_OFFSET);
ProgramResult program =
- Decompile(program_ir, Maxwell3D::Regs::ShaderStage::Geometry, "geometry");
+ Decompile(device, program_ir, Maxwell3D::Regs::ShaderStage::Geometry, "geometry");
out += program.first;
out += R"(
@@ -104,14 +90,13 @@ void main() {
execute_geometry();
};)";
- return {out, program.second};
+ return {std::move(out), std::move(program.second)};
}
-ProgramResult GenerateFragmentShader(const ShaderSetup& setup) {
+ProgramResult GenerateFragmentShader(const Device& device, const ShaderSetup& setup) {
const std::string id = fmt::format("{:016x}", setup.program.unique_identifier);
- std::string out = "#extension GL_ARB_separate_shader_objects : enable\n\n";
- out += "// Shader Unique Id: FS" + id + "\n\n";
+ std::string out = "// Shader Unique Id: FS" + id + "\n\n";
out += GetCommonDeclarations();
out += R"(
@@ -124,42 +109,15 @@ layout (location = 5) out vec4 FragColor5;
layout (location = 6) out vec4 FragColor6;
layout (location = 7) out vec4 FragColor7;
-layout (location = 0) in noperspective vec4 position;
-
layout (std140, binding = EMULATION_UBO_BINDING) uniform fs_config {
vec4 viewport_flip;
uvec4 config_pack; // instance_id, flip_stage, y_direction, padding
- uvec4 alpha_test;
};
-bool AlphaFunc(in float value) {
- float ref = uintBitsToFloat(alpha_test[2]);
- switch (alpha_test[1]) {
- case 1:
- return false;
- case 2:
- return value < ref;
- case 3:
- return value == ref;
- case 4:
- return value <= ref;
- case 5:
- return value > ref;
- case 6:
- return value != ref;
- case 7:
- return value >= ref;
- case 8:
- return true;
- default:
- return false;
- }
-}
-
)";
- ShaderIR program_ir(setup.program.code, PROGRAM_OFFSET);
+ const ShaderIR program_ir(setup.program.code, PROGRAM_OFFSET);
ProgramResult program =
- Decompile(program_ir, Maxwell3D::Regs::ShaderStage::Fragment, "fragment");
+ Decompile(device, program_ir, Maxwell3D::Regs::ShaderStage::Fragment, "fragment");
out += program.first;
@@ -169,7 +127,7 @@ void main() {
}
)";
- return {out, program.second};
+ return {std::move(out), std::move(program.second)};
}
} // namespace OpenGL::GLShader
diff --git a/src/video_core/renderer_opengl/gl_shader_gen.h b/src/video_core/renderer_opengl/gl_shader_gen.h
index fba8e681b..0536c8a03 100644
--- a/src/video_core/renderer_opengl/gl_shader_gen.h
+++ b/src/video_core/renderer_opengl/gl_shader_gen.h
@@ -4,15 +4,16 @@
#pragma once
-#include <array>
-#include <string>
#include <vector>
#include "common/common_types.h"
-#include "video_core/engines/shader_bytecode.h"
#include "video_core/renderer_opengl/gl_shader_decompiler.h"
#include "video_core/shader/shader_ir.h"
+namespace OpenGL {
+class Device;
+}
+
namespace OpenGL::GLShader {
using VideoCommon::Shader::ProgramCode;
@@ -42,22 +43,13 @@ private:
bool has_program_b{};
};
-/**
- * Generates the GLSL vertex shader program source code for the given VS program
- * @returns String of the shader source code
- */
-ProgramResult GenerateVertexShader(const ShaderSetup& setup);
-
-/**
- * Generates the GLSL geometry shader program source code for the given GS program
- * @returns String of the shader source code
- */
-ProgramResult GenerateGeometryShader(const ShaderSetup& setup);
-
-/**
- * Generates the GLSL fragment shader program source code for the given FS program
- * @returns String of the shader source code
- */
-ProgramResult GenerateFragmentShader(const ShaderSetup& setup);
+/// Generates the GLSL vertex shader program source code for the given VS program
+ProgramResult GenerateVertexShader(const Device& device, const ShaderSetup& setup);
+
+/// Generates the GLSL geometry shader program source code for the given GS program
+ProgramResult GenerateGeometryShader(const Device& device, const ShaderSetup& setup);
+
+/// Generates the GLSL fragment shader program source code for the given FS program
+ProgramResult GenerateFragmentShader(const Device& device, const ShaderSetup& setup);
} // namespace OpenGL::GLShader
diff --git a/src/video_core/renderer_opengl/gl_shader_manager.cpp b/src/video_core/renderer_opengl/gl_shader_manager.cpp
index 6a30c28d2..b05f90f20 100644
--- a/src/video_core/renderer_opengl/gl_shader_manager.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_manager.cpp
@@ -2,37 +2,59 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include "core/core.h"
+#include "common/common_types.h"
+#include "video_core/engines/maxwell_3d.h"
#include "video_core/renderer_opengl/gl_shader_manager.h"
namespace OpenGL::GLShader {
-void MaxwellUniformData::SetFromRegs(const Maxwell3D::State::ShaderStageInfo& shader_stage) {
- const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
- const auto& regs = gpu.regs;
- const auto& state = gpu.state;
+using Tegra::Engines::Maxwell3D;
- // TODO(bunnei): Support more than one viewport
- viewport_flip[0] = regs.viewport_transform[0].scale_x < 0.0 ? -1.0f : 1.0f;
- viewport_flip[1] = regs.viewport_transform[0].scale_y < 0.0 ? -1.0f : 1.0f;
+ProgramManager::ProgramManager() {
+ pipeline.Create();
+}
- u32 func = static_cast<u32>(regs.alpha_test_func);
- // Normalize the gl variants of opCompare to be the same as the normal variants
- u32 op_gl_variant_base = static_cast<u32>(Tegra::Engines::Maxwell3D::Regs::ComparisonOp::Never);
- if (func >= op_gl_variant_base) {
- func = func - op_gl_variant_base + 1U;
+ProgramManager::~ProgramManager() = default;
+
+void ProgramManager::ApplyTo(OpenGLState& state) {
+ UpdatePipeline();
+ state.draw.shader_program = 0;
+ state.draw.program_pipeline = pipeline.handle;
+}
+
+void ProgramManager::UpdatePipeline() {
+ // Avoid updating the pipeline when values have no changed
+ if (old_state == current_state) {
+ return;
}
- alpha_test.enabled = regs.alpha_test_enabled;
- alpha_test.func = func;
- alpha_test.ref = regs.alpha_test_ref;
+ // Workaround for AMD bug
+ constexpr GLenum all_used_stages{GL_VERTEX_SHADER_BIT | GL_GEOMETRY_SHADER_BIT |
+ GL_FRAGMENT_SHADER_BIT};
+ glUseProgramStages(pipeline.handle, all_used_stages, 0);
+
+ glUseProgramStages(pipeline.handle, GL_VERTEX_SHADER_BIT, current_state.vertex_shader);
+ glUseProgramStages(pipeline.handle, GL_GEOMETRY_SHADER_BIT, current_state.geometry_shader);
+ glUseProgramStages(pipeline.handle, GL_FRAGMENT_SHADER_BIT, current_state.fragment_shader);
+
+ old_state = current_state;
+}
+
+void MaxwellUniformData::SetFromRegs(const Maxwell3D& maxwell, std::size_t shader_stage) {
+ const auto& regs = maxwell.regs;
+ const auto& state = maxwell.state;
+
+ // TODO(bunnei): Support more than one viewport
+ viewport_flip[0] = regs.viewport_transform[0].scale_x < 0.0 ? -1.0f : 1.0f;
+ viewport_flip[1] = regs.viewport_transform[0].scale_y < 0.0 ? -1.0f : 1.0f;
instance_id = state.current_instance;
// Assign in which stage the position has to be flipped
// (the last stage before the fragment shader).
- if (gpu.regs.shader_config[static_cast<u32>(Maxwell3D::Regs::ShaderProgram::Geometry)].enable) {
- flip_stage = static_cast<u32>(Maxwell3D::Regs::ShaderProgram::Geometry);
+ constexpr u32 geometry_index = static_cast<u32>(Maxwell3D::Regs::ShaderProgram::Geometry);
+ if (maxwell.regs.shader_config[geometry_index].enable) {
+ flip_stage = geometry_index;
} else {
flip_stage = static_cast<u32>(Maxwell3D::Regs::ShaderProgram::VertexB);
}
diff --git a/src/video_core/renderer_opengl/gl_shader_manager.h b/src/video_core/renderer_opengl/gl_shader_manager.h
index 4970aafed..6961e702a 100644
--- a/src/video_core/renderer_opengl/gl_shader_manager.h
+++ b/src/video_core/renderer_opengl/gl_shader_manager.h
@@ -4,6 +4,8 @@
#pragma once
+#include <cstddef>
+
#include <glad/glad.h>
#include "video_core/renderer_opengl/gl_resource_manager.h"
@@ -12,83 +14,68 @@
namespace OpenGL::GLShader {
-using Tegra::Engines::Maxwell3D;
-
/// Uniform structure for the Uniform Buffer Object, all vectors must be 16-byte aligned
-// NOTE: Always keep a vec4 at the end. The GL spec is not clear whether the alignment at
-// the end of a uniform block is included in UNIFORM_BLOCK_DATA_SIZE or not.
-// Not following that rule will cause problems on some AMD drivers.
+/// @note Always keep a vec4 at the end. The GL spec is not clear whether the alignment at
+/// the end of a uniform block is included in UNIFORM_BLOCK_DATA_SIZE or not.
+/// Not following that rule will cause problems on some AMD drivers.
struct MaxwellUniformData {
- void SetFromRegs(const Maxwell3D::State::ShaderStageInfo& shader_stage);
+ void SetFromRegs(const Tegra::Engines::Maxwell3D& maxwell, std::size_t shader_stage);
+
alignas(16) GLvec4 viewport_flip;
struct alignas(16) {
GLuint instance_id;
GLuint flip_stage;
GLfloat y_direction;
};
- struct alignas(16) {
- GLuint enabled;
- GLuint func;
- GLfloat ref;
- GLuint padding;
- } alpha_test;
};
-static_assert(sizeof(MaxwellUniformData) == 48, "MaxwellUniformData structure size is incorrect");
+static_assert(sizeof(MaxwellUniformData) == 32, "MaxwellUniformData structure size is incorrect");
static_assert(sizeof(MaxwellUniformData) < 16384,
"MaxwellUniformData structure must be less than 16kb as per the OpenGL spec");
class ProgramManager {
public:
- ProgramManager() {
- pipeline.Create();
- }
+ explicit ProgramManager();
+ ~ProgramManager();
+
+ void ApplyTo(OpenGLState& state);
void UseProgrammableVertexShader(GLuint program) {
- vs = program;
+ current_state.vertex_shader = program;
}
void UseProgrammableGeometryShader(GLuint program) {
- gs = program;
+ current_state.geometry_shader = program;
}
void UseProgrammableFragmentShader(GLuint program) {
- fs = program;
+ current_state.fragment_shader = program;
}
void UseTrivialGeometryShader() {
- gs = 0;
- }
-
- void ApplyTo(OpenGLState& state) {
- UpdatePipeline();
- state.draw.shader_program = 0;
- state.draw.program_pipeline = pipeline.handle;
- state.geometry_shaders.enabled = (gs != 0);
+ current_state.geometry_shader = 0;
}
private:
- void UpdatePipeline() {
- // Avoid updating the pipeline when values have no changed
- if (old_vs == vs && old_fs == fs && old_gs == gs)
- return;
- // Workaround for AMD bug
- glUseProgramStages(pipeline.handle,
- GL_VERTEX_SHADER_BIT | GL_GEOMETRY_SHADER_BIT | GL_FRAGMENT_SHADER_BIT,
- 0);
-
- glUseProgramStages(pipeline.handle, GL_VERTEX_SHADER_BIT, vs);
- glUseProgramStages(pipeline.handle, GL_GEOMETRY_SHADER_BIT, gs);
- glUseProgramStages(pipeline.handle, GL_FRAGMENT_SHADER_BIT, fs);
-
- // Update the old values
- old_vs = vs;
- old_fs = fs;
- old_gs = gs;
- }
+ struct PipelineState {
+ bool operator==(const PipelineState& rhs) const {
+ return vertex_shader == rhs.vertex_shader && fragment_shader == rhs.fragment_shader &&
+ geometry_shader == rhs.geometry_shader;
+ }
+
+ bool operator!=(const PipelineState& rhs) const {
+ return !operator==(rhs);
+ }
+
+ GLuint vertex_shader{};
+ GLuint fragment_shader{};
+ GLuint geometry_shader{};
+ };
+
+ void UpdatePipeline();
OGLPipeline pipeline;
- GLuint vs{}, fs{}, gs{};
- GLuint old_vs{}, old_fs{}, old_gs{};
+ PipelineState current_state;
+ PipelineState old_state;
};
} // namespace OpenGL::GLShader
diff --git a/src/video_core/renderer_opengl/gl_state.cpp b/src/video_core/renderer_opengl/gl_state.cpp
index 9419326a3..d86e137ac 100644
--- a/src/video_core/renderer_opengl/gl_state.cpp
+++ b/src/video_core/renderer_opengl/gl_state.cpp
@@ -10,16 +10,62 @@
namespace OpenGL {
-OpenGLState OpenGLState::cur_state;
+using Maxwell = Tegra::Engines::Maxwell3D::Regs;
+OpenGLState OpenGLState::cur_state;
bool OpenGLState::s_rgb_used;
+namespace {
+
+template <typename T>
+bool UpdateValue(T& current_value, const T new_value) {
+ const bool changed = current_value != new_value;
+ current_value = new_value;
+ return changed;
+}
+
+template <typename T1, typename T2>
+bool UpdateTie(T1 current_value, const T2 new_value) {
+ const bool changed = current_value != new_value;
+ current_value = new_value;
+ return changed;
+}
+
+void Enable(GLenum cap, bool enable) {
+ if (enable) {
+ glEnable(cap);
+ } else {
+ glDisable(cap);
+ }
+}
+
+void Enable(GLenum cap, GLuint index, bool enable) {
+ if (enable) {
+ glEnablei(cap, index);
+ } else {
+ glDisablei(cap, index);
+ }
+}
+
+void Enable(GLenum cap, bool& current_value, bool new_value) {
+ if (UpdateValue(current_value, new_value))
+ Enable(cap, new_value);
+}
+
+void Enable(GLenum cap, GLuint index, bool& current_value, bool new_value) {
+ if (UpdateValue(current_value, new_value))
+ Enable(cap, index, new_value);
+}
+
+} // namespace
+
OpenGLState::OpenGLState() {
// These all match default OpenGL values
- geometry_shaders.enabled = false;
framebuffer_srgb.enabled = false;
+
multisample_control.alpha_to_coverage = false;
multisample_control.alpha_to_one = false;
+
cull.enabled = false;
cull.mode = GL_BACK;
cull.front_face = GL_CCW;
@@ -30,14 +76,15 @@ OpenGLState::OpenGLState() {
primitive_restart.enabled = false;
primitive_restart.index = 0;
+
for (auto& item : color_mask) {
item.red_enabled = GL_TRUE;
item.green_enabled = GL_TRUE;
item.blue_enabled = GL_TRUE;
item.alpha_enabled = GL_TRUE;
}
- stencil.test_enabled = false;
- auto reset_stencil = [](auto& config) {
+
+ const auto ResetStencil = [](auto& config) {
config.test_func = GL_ALWAYS;
config.test_ref = 0;
config.test_mask = 0xFFFFFFFF;
@@ -46,8 +93,10 @@ OpenGLState::OpenGLState() {
config.action_depth_pass = GL_KEEP;
config.action_stencil_fail = GL_KEEP;
};
- reset_stencil(stencil.front);
- reset_stencil(stencil.back);
+ stencil.test_enabled = false;
+ ResetStencil(stencil.front);
+ ResetStencil(stencil.back);
+
for (auto& item : viewports) {
item.x = 0;
item.y = 0;
@@ -61,6 +110,7 @@ OpenGLState::OpenGLState() {
item.scissor.width = 0;
item.scissor.height = 0;
}
+
for (auto& item : blend) {
item.enabled = true;
item.rgb_equation = GL_FUNC_ADD;
@@ -70,11 +120,14 @@ OpenGLState::OpenGLState() {
item.src_a_func = GL_ONE;
item.dst_a_func = GL_ZERO;
}
+
independant_blend.enabled = false;
+
blend_color.red = 0.0f;
blend_color.green = 0.0f;
blend_color.blue = 0.0f;
blend_color.alpha = 0.0f;
+
logic_op.enabled = false;
logic_op.operation = GL_COPY;
@@ -91,272 +144,274 @@ OpenGLState::OpenGLState() {
clip_distance = {};
point.size = 1;
+
fragment_color_clamp.enabled = false;
+
depth_clamp.far_plane = false;
depth_clamp.near_plane = false;
+
polygon_offset.fill_enable = false;
polygon_offset.line_enable = false;
polygon_offset.point_enable = false;
polygon_offset.factor = 0.0f;
polygon_offset.units = 0.0f;
polygon_offset.clamp = 0.0f;
+
+ alpha_test.enabled = false;
+ alpha_test.func = GL_ALWAYS;
+ alpha_test.ref = 0.0f;
}
void OpenGLState::ApplyDefaultState() {
+ glEnable(GL_BLEND);
glDisable(GL_FRAMEBUFFER_SRGB);
glDisable(GL_CULL_FACE);
glDisable(GL_DEPTH_TEST);
glDisable(GL_PRIMITIVE_RESTART);
glDisable(GL_STENCIL_TEST);
- glEnable(GL_BLEND);
glDisable(GL_COLOR_LOGIC_OP);
glDisable(GL_SCISSOR_TEST);
}
+void OpenGLState::ApplyFramebufferState() const {
+ if (UpdateValue(cur_state.draw.read_framebuffer, draw.read_framebuffer)) {
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, draw.read_framebuffer);
+ }
+ if (UpdateValue(cur_state.draw.draw_framebuffer, draw.draw_framebuffer)) {
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, draw.draw_framebuffer);
+ }
+}
+
+void OpenGLState::ApplyVertexArrayState() const {
+ if (UpdateValue(cur_state.draw.vertex_array, draw.vertex_array)) {
+ glBindVertexArray(draw.vertex_array);
+ }
+}
+
+void OpenGLState::ApplyShaderProgram() const {
+ if (UpdateValue(cur_state.draw.shader_program, draw.shader_program)) {
+ glUseProgram(draw.shader_program);
+ }
+}
+
+void OpenGLState::ApplyProgramPipeline() const {
+ if (UpdateValue(cur_state.draw.program_pipeline, draw.program_pipeline)) {
+ glBindProgramPipeline(draw.program_pipeline);
+ }
+}
+
+void OpenGLState::ApplyClipDistances() const {
+ for (std::size_t i = 0; i < clip_distance.size(); ++i) {
+ Enable(GL_CLIP_DISTANCE0 + static_cast<GLenum>(i), cur_state.clip_distance[i],
+ clip_distance[i]);
+ }
+}
+
+void OpenGLState::ApplyPointSize() const {
+ if (UpdateValue(cur_state.point.size, point.size)) {
+ glPointSize(point.size);
+ }
+}
+
+void OpenGLState::ApplyFragmentColorClamp() const {
+ if (UpdateValue(cur_state.fragment_color_clamp.enabled, fragment_color_clamp.enabled)) {
+ glClampColor(GL_CLAMP_FRAGMENT_COLOR_ARB,
+ fragment_color_clamp.enabled ? GL_TRUE : GL_FALSE);
+ }
+}
+
+void OpenGLState::ApplyMultisample() const {
+ Enable(GL_SAMPLE_ALPHA_TO_COVERAGE, cur_state.multisample_control.alpha_to_coverage,
+ multisample_control.alpha_to_coverage);
+ Enable(GL_SAMPLE_ALPHA_TO_ONE, cur_state.multisample_control.alpha_to_one,
+ multisample_control.alpha_to_one);
+}
+
+void OpenGLState::ApplyDepthClamp() const {
+ if (depth_clamp.far_plane == cur_state.depth_clamp.far_plane &&
+ depth_clamp.near_plane == cur_state.depth_clamp.near_plane) {
+ return;
+ }
+ cur_state.depth_clamp = depth_clamp;
+
+ UNIMPLEMENTED_IF_MSG(depth_clamp.far_plane != depth_clamp.near_plane,
+ "Unimplemented Depth Clamp Separation!");
+
+ Enable(GL_DEPTH_CLAMP, depth_clamp.far_plane || depth_clamp.near_plane);
+}
+
void OpenGLState::ApplySRgb() const {
- if (framebuffer_srgb.enabled != cur_state.framebuffer_srgb.enabled) {
- if (framebuffer_srgb.enabled) {
- // Track if sRGB is used
- s_rgb_used = true;
- glEnable(GL_FRAMEBUFFER_SRGB);
- } else {
- glDisable(GL_FRAMEBUFFER_SRGB);
- }
+ if (cur_state.framebuffer_srgb.enabled == framebuffer_srgb.enabled)
+ return;
+ cur_state.framebuffer_srgb.enabled = framebuffer_srgb.enabled;
+ if (framebuffer_srgb.enabled) {
+ // Track if sRGB is used
+ s_rgb_used = true;
+ glEnable(GL_FRAMEBUFFER_SRGB);
+ } else {
+ glDisable(GL_FRAMEBUFFER_SRGB);
}
}
void OpenGLState::ApplyCulling() const {
- if (cull.enabled != cur_state.cull.enabled) {
- if (cull.enabled) {
- glEnable(GL_CULL_FACE);
- } else {
- glDisable(GL_CULL_FACE);
- }
- }
+ Enable(GL_CULL_FACE, cur_state.cull.enabled, cull.enabled);
- if (cull.mode != cur_state.cull.mode) {
+ if (UpdateValue(cur_state.cull.mode, cull.mode)) {
glCullFace(cull.mode);
}
- if (cull.front_face != cur_state.cull.front_face) {
+ if (UpdateValue(cur_state.cull.front_face, cull.front_face)) {
glFrontFace(cull.front_face);
}
}
void OpenGLState::ApplyColorMask() const {
- if (independant_blend.enabled) {
- for (size_t i = 0; i < Tegra::Engines::Maxwell3D::Regs::NumRenderTargets; i++) {
- const auto& updated = color_mask[i];
- const auto& current = cur_state.color_mask[i];
- if (updated.red_enabled != current.red_enabled ||
- updated.green_enabled != current.green_enabled ||
- updated.blue_enabled != current.blue_enabled ||
- updated.alpha_enabled != current.alpha_enabled) {
- glColorMaski(static_cast<GLuint>(i), updated.red_enabled, updated.green_enabled,
- updated.blue_enabled, updated.alpha_enabled);
- }
- }
- } else {
- const auto& updated = color_mask[0];
- const auto& current = cur_state.color_mask[0];
+ for (std::size_t i = 0; i < Maxwell::NumRenderTargets; ++i) {
+ const auto& updated = color_mask[i];
+ auto& current = cur_state.color_mask[i];
if (updated.red_enabled != current.red_enabled ||
updated.green_enabled != current.green_enabled ||
updated.blue_enabled != current.blue_enabled ||
updated.alpha_enabled != current.alpha_enabled) {
- glColorMask(updated.red_enabled, updated.green_enabled, updated.blue_enabled,
- updated.alpha_enabled);
+ current = updated;
+ glColorMaski(static_cast<GLuint>(i), updated.red_enabled, updated.green_enabled,
+ updated.blue_enabled, updated.alpha_enabled);
}
}
}
void OpenGLState::ApplyDepth() const {
- if (depth.test_enabled != cur_state.depth.test_enabled) {
- if (depth.test_enabled) {
- glEnable(GL_DEPTH_TEST);
- } else {
- glDisable(GL_DEPTH_TEST);
- }
- }
+ Enable(GL_DEPTH_TEST, cur_state.depth.test_enabled, depth.test_enabled);
- if (depth.test_func != cur_state.depth.test_func) {
+ if (cur_state.depth.test_func != depth.test_func) {
+ cur_state.depth.test_func = depth.test_func;
glDepthFunc(depth.test_func);
}
- if (depth.write_mask != cur_state.depth.write_mask) {
+ if (cur_state.depth.write_mask != depth.write_mask) {
+ cur_state.depth.write_mask = depth.write_mask;
glDepthMask(depth.write_mask);
}
}
void OpenGLState::ApplyPrimitiveRestart() const {
- if (primitive_restart.enabled != cur_state.primitive_restart.enabled) {
- if (primitive_restart.enabled) {
- glEnable(GL_PRIMITIVE_RESTART);
- } else {
- glDisable(GL_PRIMITIVE_RESTART);
- }
- }
+ Enable(GL_PRIMITIVE_RESTART, cur_state.primitive_restart.enabled, primitive_restart.enabled);
- if (primitive_restart.index != cur_state.primitive_restart.index) {
+ if (cur_state.primitive_restart.index != primitive_restart.index) {
+ cur_state.primitive_restart.index = primitive_restart.index;
glPrimitiveRestartIndex(primitive_restart.index);
}
}
void OpenGLState::ApplyStencilTest() const {
- if (stencil.test_enabled != cur_state.stencil.test_enabled) {
- if (stencil.test_enabled) {
- glEnable(GL_STENCIL_TEST);
- } else {
- glDisable(GL_STENCIL_TEST);
- }
- }
-
- const auto ConfigStencil = [](GLenum face, const auto& config, const auto& prev_config) {
- if (config.test_func != prev_config.test_func || config.test_ref != prev_config.test_ref ||
- config.test_mask != prev_config.test_mask) {
+ Enable(GL_STENCIL_TEST, cur_state.stencil.test_enabled, stencil.test_enabled);
+
+ const auto ConfigStencil = [](GLenum face, const auto& config, auto& current) {
+ if (current.test_func != config.test_func || current.test_ref != config.test_ref ||
+ current.test_mask != config.test_mask) {
+ current.test_func = config.test_func;
+ current.test_ref = config.test_ref;
+ current.test_mask = config.test_mask;
glStencilFuncSeparate(face, config.test_func, config.test_ref, config.test_mask);
}
- if (config.action_depth_fail != prev_config.action_depth_fail ||
- config.action_depth_pass != prev_config.action_depth_pass ||
- config.action_stencil_fail != prev_config.action_stencil_fail) {
+ if (current.action_depth_fail != config.action_depth_fail ||
+ current.action_depth_pass != config.action_depth_pass ||
+ current.action_stencil_fail != config.action_stencil_fail) {
+ current.action_depth_fail = config.action_depth_fail;
+ current.action_depth_pass = config.action_depth_pass;
+ current.action_stencil_fail = config.action_stencil_fail;
glStencilOpSeparate(face, config.action_stencil_fail, config.action_depth_fail,
config.action_depth_pass);
}
- if (config.write_mask != prev_config.write_mask) {
+ if (current.write_mask != config.write_mask) {
+ current.write_mask = config.write_mask;
glStencilMaskSeparate(face, config.write_mask);
}
};
ConfigStencil(GL_FRONT, stencil.front, cur_state.stencil.front);
ConfigStencil(GL_BACK, stencil.back, cur_state.stencil.back);
}
-// Viewport does not affects glClearBuffer so emulate viewport using scissor test
-void OpenGLState::EmulateViewportWithScissor() {
- auto& current = viewports[0];
- if (current.scissor.enabled) {
- const GLint left = std::max(current.x, current.scissor.x);
- const GLint right =
- std::max(current.x + current.width, current.scissor.x + current.scissor.width);
- const GLint bottom = std::max(current.y, current.scissor.y);
- const GLint top =
- std::max(current.y + current.height, current.scissor.y + current.scissor.height);
- current.scissor.x = std::max(left, 0);
- current.scissor.y = std::max(bottom, 0);
- current.scissor.width = std::max(right - left, 0);
- current.scissor.height = std::max(top - bottom, 0);
- } else {
- current.scissor.enabled = true;
- current.scissor.x = current.x;
- current.scissor.y = current.y;
- current.scissor.width = current.width;
- current.scissor.height = current.height;
- }
-}
void OpenGLState::ApplyViewport() const {
- if (geometry_shaders.enabled) {
- for (GLuint i = 0; i < static_cast<GLuint>(Tegra::Engines::Maxwell3D::Regs::NumViewports);
- i++) {
- const auto& current = cur_state.viewports[i];
- const auto& updated = viewports[i];
- if (updated.x != current.x || updated.y != current.y ||
- updated.width != current.width || updated.height != current.height) {
- glViewportIndexedf(
- i, static_cast<GLfloat>(updated.x), static_cast<GLfloat>(updated.y),
- static_cast<GLfloat>(updated.width), static_cast<GLfloat>(updated.height));
- }
- if (updated.depth_range_near != current.depth_range_near ||
- updated.depth_range_far != current.depth_range_far) {
- glDepthRangeIndexed(i, updated.depth_range_near, updated.depth_range_far);
- }
-
- if (updated.scissor.enabled != current.scissor.enabled) {
- if (updated.scissor.enabled) {
- glEnablei(GL_SCISSOR_TEST, i);
- } else {
- glDisablei(GL_SCISSOR_TEST, i);
- }
- }
-
- if (updated.scissor.x != current.scissor.x || updated.scissor.y != current.scissor.y ||
- updated.scissor.width != current.scissor.width ||
- updated.scissor.height != current.scissor.height) {
- glScissorIndexed(i, updated.scissor.x, updated.scissor.y, updated.scissor.width,
- updated.scissor.height);
- }
+ for (GLuint i = 0; i < static_cast<GLuint>(Maxwell::NumViewports); ++i) {
+ const auto& updated = viewports[i];
+ auto& current = cur_state.viewports[i];
+
+ if (current.x != updated.x || current.y != updated.y || current.width != updated.width ||
+ current.height != updated.height) {
+ current.x = updated.x;
+ current.y = updated.y;
+ current.width = updated.width;
+ current.height = updated.height;
+ glViewportIndexedf(i, static_cast<GLfloat>(updated.x), static_cast<GLfloat>(updated.y),
+ static_cast<GLfloat>(updated.width),
+ static_cast<GLfloat>(updated.height));
}
- } else {
- const auto& current = cur_state.viewports[0];
- const auto& updated = viewports[0];
- if (updated.x != current.x || updated.y != current.y || updated.width != current.width ||
- updated.height != current.height) {
- glViewport(updated.x, updated.y, updated.width, updated.height);
- }
-
- if (updated.depth_range_near != current.depth_range_near ||
- updated.depth_range_far != current.depth_range_far) {
- glDepthRange(updated.depth_range_near, updated.depth_range_far);
+ if (current.depth_range_near != updated.depth_range_near ||
+ current.depth_range_far != updated.depth_range_far) {
+ current.depth_range_near = updated.depth_range_near;
+ current.depth_range_far = updated.depth_range_far;
+ glDepthRangeIndexed(i, updated.depth_range_near, updated.depth_range_far);
}
- if (updated.scissor.enabled != current.scissor.enabled) {
- if (updated.scissor.enabled) {
- glEnable(GL_SCISSOR_TEST);
- } else {
- glDisable(GL_SCISSOR_TEST);
- }
- }
-
- if (updated.scissor.x != current.scissor.x || updated.scissor.y != current.scissor.y ||
- updated.scissor.width != current.scissor.width ||
- updated.scissor.height != current.scissor.height) {
- glScissor(updated.scissor.x, updated.scissor.y, updated.scissor.width,
- updated.scissor.height);
+ Enable(GL_SCISSOR_TEST, i, current.scissor.enabled, updated.scissor.enabled);
+
+ if (current.scissor.x != updated.scissor.x || current.scissor.y != updated.scissor.y ||
+ current.scissor.width != updated.scissor.width ||
+ current.scissor.height != updated.scissor.height) {
+ current.scissor.x = updated.scissor.x;
+ current.scissor.y = updated.scissor.y;
+ current.scissor.width = updated.scissor.width;
+ current.scissor.height = updated.scissor.height;
+ glScissorIndexed(i, updated.scissor.x, updated.scissor.y, updated.scissor.width,
+ updated.scissor.height);
}
}
}
void OpenGLState::ApplyGlobalBlending() const {
- const Blend& current = cur_state.blend[0];
const Blend& updated = blend[0];
- if (updated.enabled != current.enabled) {
- if (updated.enabled) {
- glEnable(GL_BLEND);
- } else {
- glDisable(GL_BLEND);
- }
- }
- if (!updated.enabled) {
- return;
- }
- if (updated.src_rgb_func != current.src_rgb_func ||
- updated.dst_rgb_func != current.dst_rgb_func || updated.src_a_func != current.src_a_func ||
- updated.dst_a_func != current.dst_a_func) {
+ Blend& current = cur_state.blend[0];
+
+ Enable(GL_BLEND, current.enabled, updated.enabled);
+
+ if (current.src_rgb_func != updated.src_rgb_func ||
+ current.dst_rgb_func != updated.dst_rgb_func || current.src_a_func != updated.src_a_func ||
+ current.dst_a_func != updated.dst_a_func) {
+ current.src_rgb_func = updated.src_rgb_func;
+ current.dst_rgb_func = updated.dst_rgb_func;
+ current.src_a_func = updated.src_a_func;
+ current.dst_a_func = updated.dst_a_func;
glBlendFuncSeparate(updated.src_rgb_func, updated.dst_rgb_func, updated.src_a_func,
updated.dst_a_func);
}
- if (updated.rgb_equation != current.rgb_equation || updated.a_equation != current.a_equation) {
+ if (current.rgb_equation != updated.rgb_equation || current.a_equation != updated.a_equation) {
+ current.rgb_equation = updated.rgb_equation;
+ current.a_equation = updated.a_equation;
glBlendEquationSeparate(updated.rgb_equation, updated.a_equation);
}
}
void OpenGLState::ApplyTargetBlending(std::size_t target, bool force) const {
const Blend& updated = blend[target];
- const Blend& current = cur_state.blend[target];
- if (updated.enabled != current.enabled || force) {
- if (updated.enabled) {
- glEnablei(GL_BLEND, static_cast<GLuint>(target));
- } else {
- glDisablei(GL_BLEND, static_cast<GLuint>(target));
- }
+ Blend& current = cur_state.blend[target];
+
+ if (current.enabled != updated.enabled || force) {
+ current.enabled = updated.enabled;
+ Enable(GL_BLEND, static_cast<GLuint>(target), updated.enabled);
}
- if (updated.src_rgb_func != current.src_rgb_func ||
- updated.dst_rgb_func != current.dst_rgb_func || updated.src_a_func != current.src_a_func ||
- updated.dst_a_func != current.dst_a_func) {
+ if (UpdateTie(std::tie(current.src_rgb_func, current.dst_rgb_func, current.src_a_func,
+ current.dst_a_func),
+ std::tie(updated.src_rgb_func, updated.dst_rgb_func, updated.src_a_func,
+ updated.dst_a_func))) {
glBlendFuncSeparatei(static_cast<GLuint>(target), updated.src_rgb_func,
updated.dst_rgb_func, updated.src_a_func, updated.dst_a_func);
}
- if (updated.rgb_equation != current.rgb_equation || updated.a_equation != current.a_equation) {
+ if (UpdateTie(std::tie(current.rgb_equation, current.a_equation),
+ std::tie(updated.rgb_equation, updated.a_equation))) {
glBlendEquationSeparatei(static_cast<GLuint>(target), updated.rgb_equation,
updated.a_equation);
}
@@ -364,101 +419,80 @@ void OpenGLState::ApplyTargetBlending(std::size_t target, bool force) const {
void OpenGLState::ApplyBlending() const {
if (independant_blend.enabled) {
- for (size_t i = 0; i < Tegra::Engines::Maxwell3D::Regs::NumRenderTargets; i++) {
- ApplyTargetBlending(i,
- independant_blend.enabled != cur_state.independant_blend.enabled);
+ const bool force = independant_blend.enabled != cur_state.independant_blend.enabled;
+ for (std::size_t target = 0; target < Maxwell::NumRenderTargets; ++target) {
+ ApplyTargetBlending(target, force);
}
} else {
ApplyGlobalBlending();
}
- if (blend_color.red != cur_state.blend_color.red ||
- blend_color.green != cur_state.blend_color.green ||
- blend_color.blue != cur_state.blend_color.blue ||
- blend_color.alpha != cur_state.blend_color.alpha) {
+ cur_state.independant_blend.enabled = independant_blend.enabled;
+
+ if (UpdateTie(
+ std::tie(cur_state.blend_color.red, cur_state.blend_color.green,
+ cur_state.blend_color.blue, cur_state.blend_color.alpha),
+ std::tie(blend_color.red, blend_color.green, blend_color.blue, blend_color.alpha))) {
glBlendColor(blend_color.red, blend_color.green, blend_color.blue, blend_color.alpha);
}
}
void OpenGLState::ApplyLogicOp() const {
- if (logic_op.enabled != cur_state.logic_op.enabled) {
- if (logic_op.enabled) {
- glEnable(GL_COLOR_LOGIC_OP);
- } else {
- glDisable(GL_COLOR_LOGIC_OP);
- }
- }
+ Enable(GL_COLOR_LOGIC_OP, cur_state.logic_op.enabled, logic_op.enabled);
- if (logic_op.operation != cur_state.logic_op.operation) {
+ if (UpdateValue(cur_state.logic_op.operation, logic_op.operation)) {
glLogicOp(logic_op.operation);
}
}
void OpenGLState::ApplyPolygonOffset() const {
- const bool fill_enable_changed =
- polygon_offset.fill_enable != cur_state.polygon_offset.fill_enable;
- const bool line_enable_changed =
- polygon_offset.line_enable != cur_state.polygon_offset.line_enable;
- const bool point_enable_changed =
- polygon_offset.point_enable != cur_state.polygon_offset.point_enable;
- const bool factor_changed = polygon_offset.factor != cur_state.polygon_offset.factor;
- const bool units_changed = polygon_offset.units != cur_state.polygon_offset.units;
- const bool clamp_changed = polygon_offset.clamp != cur_state.polygon_offset.clamp;
-
- if (fill_enable_changed) {
- if (polygon_offset.fill_enable) {
- glEnable(GL_POLYGON_OFFSET_FILL);
- } else {
- glDisable(GL_POLYGON_OFFSET_FILL);
- }
- }
-
- if (line_enable_changed) {
- if (polygon_offset.line_enable) {
- glEnable(GL_POLYGON_OFFSET_LINE);
- } else {
- glDisable(GL_POLYGON_OFFSET_LINE);
- }
- }
-
- if (point_enable_changed) {
- if (polygon_offset.point_enable) {
- glEnable(GL_POLYGON_OFFSET_POINT);
- } else {
- glDisable(GL_POLYGON_OFFSET_POINT);
- }
- }
-
- if (factor_changed || units_changed || clamp_changed) {
+ Enable(GL_POLYGON_OFFSET_FILL, cur_state.polygon_offset.fill_enable,
+ polygon_offset.fill_enable);
+ Enable(GL_POLYGON_OFFSET_LINE, cur_state.polygon_offset.line_enable,
+ polygon_offset.line_enable);
+ Enable(GL_POLYGON_OFFSET_POINT, cur_state.polygon_offset.point_enable,
+ polygon_offset.point_enable);
+
+ if (UpdateTie(std::tie(cur_state.polygon_offset.factor, cur_state.polygon_offset.units,
+ cur_state.polygon_offset.clamp),
+ std::tie(polygon_offset.factor, polygon_offset.units, polygon_offset.clamp))) {
if (GLAD_GL_EXT_polygon_offset_clamp && polygon_offset.clamp != 0) {
glPolygonOffsetClamp(polygon_offset.factor, polygon_offset.units, polygon_offset.clamp);
} else {
- glPolygonOffset(polygon_offset.factor, polygon_offset.units);
UNIMPLEMENTED_IF_MSG(polygon_offset.clamp != 0,
"Unimplemented Depth polygon offset clamp.");
+ glPolygonOffset(polygon_offset.factor, polygon_offset.units);
}
}
}
+void OpenGLState::ApplyAlphaTest() const {
+ Enable(GL_ALPHA_TEST, cur_state.alpha_test.enabled, alpha_test.enabled);
+ if (UpdateTie(std::tie(cur_state.alpha_test.func, cur_state.alpha_test.ref),
+ std::tie(alpha_test.func, alpha_test.ref))) {
+ glAlphaFunc(alpha_test.func, alpha_test.ref);
+ }
+}
+
void OpenGLState::ApplyTextures() const {
bool has_delta{};
std::size_t first{};
std::size_t last{};
- std::array<GLuint, Tegra::Engines::Maxwell3D::Regs::NumTextureSamplers> textures;
+ std::array<GLuint, Maxwell::NumTextureSamplers> textures;
for (std::size_t i = 0; i < std::size(texture_units); ++i) {
const auto& texture_unit = texture_units[i];
- const auto& cur_state_texture_unit = cur_state.texture_units[i];
+ auto& cur_state_texture_unit = cur_state.texture_units[i];
textures[i] = texture_unit.texture;
-
- if (textures[i] != cur_state_texture_unit.texture) {
- if (!has_delta) {
- first = i;
- has_delta = true;
- }
- last = i;
+ if (cur_state_texture_unit.texture == textures[i]) {
+ continue;
+ }
+ cur_state_texture_unit.texture = textures[i];
+ if (!has_delta) {
+ first = i;
+ has_delta = true;
}
+ last = i;
}
-
if (has_delta) {
glBindTextures(static_cast<GLuint>(first), static_cast<GLsizei>(last - first + 1),
textures.data() + first);
@@ -469,16 +503,19 @@ void OpenGLState::ApplySamplers() const {
bool has_delta{};
std::size_t first{};
std::size_t last{};
- std::array<GLuint, Tegra::Engines::Maxwell3D::Regs::NumTextureSamplers> samplers;
+ std::array<GLuint, Maxwell::NumTextureSamplers> samplers;
+
for (std::size_t i = 0; i < std::size(samplers); ++i) {
samplers[i] = texture_units[i].sampler;
- if (samplers[i] != cur_state.texture_units[i].sampler) {
- if (!has_delta) {
- first = i;
- has_delta = true;
- }
- last = i;
+ if (cur_state.texture_units[i].sampler == texture_units[i].sampler) {
+ continue;
}
+ cur_state.texture_units[i].sampler = texture_units[i].sampler;
+ if (!has_delta) {
+ first = i;
+ has_delta = true;
+ }
+ last = i;
}
if (has_delta) {
glBindSamplers(static_cast<GLuint>(first), static_cast<GLsizei>(last - first + 1),
@@ -486,81 +523,15 @@ void OpenGLState::ApplySamplers() const {
}
}
-void OpenGLState::ApplyFramebufferState() const {
- if (draw.read_framebuffer != cur_state.draw.read_framebuffer) {
- glBindFramebuffer(GL_READ_FRAMEBUFFER, draw.read_framebuffer);
- }
- if (draw.draw_framebuffer != cur_state.draw.draw_framebuffer) {
- glBindFramebuffer(GL_DRAW_FRAMEBUFFER, draw.draw_framebuffer);
- }
-}
-
-void OpenGLState::ApplyVertexArrayState() const {
- if (draw.vertex_array != cur_state.draw.vertex_array) {
- glBindVertexArray(draw.vertex_array);
- }
-}
-
-void OpenGLState::ApplyDepthClamp() const {
- if (depth_clamp.far_plane == cur_state.depth_clamp.far_plane &&
- depth_clamp.near_plane == cur_state.depth_clamp.near_plane) {
- return;
- }
- UNIMPLEMENTED_IF_MSG(depth_clamp.far_plane != depth_clamp.near_plane,
- "Unimplemented Depth Clamp Separation!");
-
- if (depth_clamp.far_plane || depth_clamp.near_plane) {
- glEnable(GL_DEPTH_CLAMP);
- } else {
- glDisable(GL_DEPTH_CLAMP);
- }
-}
-
void OpenGLState::Apply() const {
ApplyFramebufferState();
ApplyVertexArrayState();
-
- // Shader program
- if (draw.shader_program != cur_state.draw.shader_program) {
- glUseProgram(draw.shader_program);
- }
-
- // Program pipeline
- if (draw.program_pipeline != cur_state.draw.program_pipeline) {
- glBindProgramPipeline(draw.program_pipeline);
- }
- // Clip distance
- for (std::size_t i = 0; i < clip_distance.size(); ++i) {
- if (clip_distance[i] != cur_state.clip_distance[i]) {
- if (clip_distance[i]) {
- glEnable(GL_CLIP_DISTANCE0 + static_cast<GLenum>(i));
- } else {
- glDisable(GL_CLIP_DISTANCE0 + static_cast<GLenum>(i));
- }
- }
- }
- // Point
- if (point.size != cur_state.point.size) {
- glPointSize(point.size);
- }
- if (fragment_color_clamp.enabled != cur_state.fragment_color_clamp.enabled) {
- glClampColor(GL_CLAMP_FRAGMENT_COLOR_ARB,
- fragment_color_clamp.enabled ? GL_TRUE : GL_FALSE);
- }
- if (multisample_control.alpha_to_coverage != cur_state.multisample_control.alpha_to_coverage) {
- if (multisample_control.alpha_to_coverage) {
- glEnable(GL_SAMPLE_ALPHA_TO_COVERAGE);
- } else {
- glDisable(GL_SAMPLE_ALPHA_TO_COVERAGE);
- }
- }
- if (multisample_control.alpha_to_one != cur_state.multisample_control.alpha_to_one) {
- if (multisample_control.alpha_to_one) {
- glEnable(GL_SAMPLE_ALPHA_TO_ONE);
- } else {
- glDisable(GL_SAMPLE_ALPHA_TO_ONE);
- }
- }
+ ApplyShaderProgram();
+ ApplyProgramPipeline();
+ ApplyClipDistances();
+ ApplyPointSize();
+ ApplyFragmentColorClamp();
+ ApplyMultisample();
ApplyDepthClamp();
ApplyColorMask();
ApplyViewport();
@@ -574,7 +545,29 @@ void OpenGLState::Apply() const {
ApplyTextures();
ApplySamplers();
ApplyPolygonOffset();
- cur_state = *this;
+ ApplyAlphaTest();
+}
+
+void OpenGLState::EmulateViewportWithScissor() {
+ auto& current = viewports[0];
+ if (current.scissor.enabled) {
+ const GLint left = std::max(current.x, current.scissor.x);
+ const GLint right =
+ std::max(current.x + current.width, current.scissor.x + current.scissor.width);
+ const GLint bottom = std::max(current.y, current.scissor.y);
+ const GLint top =
+ std::max(current.y + current.height, current.scissor.y + current.scissor.height);
+ current.scissor.x = std::max(left, 0);
+ current.scissor.y = std::max(bottom, 0);
+ current.scissor.width = std::max(right - left, 0);
+ current.scissor.height = std::max(top - bottom, 0);
+ } else {
+ current.scissor.enabled = true;
+ current.scissor.x = current.x;
+ current.scissor.y = current.y;
+ current.scissor.width = current.width;
+ current.scissor.height = current.height;
+ }
}
OpenGLState& OpenGLState::UnbindTexture(GLuint handle) {
diff --git a/src/video_core/renderer_opengl/gl_state.h b/src/video_core/renderer_opengl/gl_state.h
index 9e1eda5b1..b0140495d 100644
--- a/src/video_core/renderer_opengl/gl_state.h
+++ b/src/video_core/renderer_opengl/gl_state.h
@@ -54,10 +54,6 @@ public:
} depth_clamp; // GL_DEPTH_CLAMP
struct {
- bool enabled; // viewports arrays are only supported when geometry shaders are enabled.
- } geometry_shaders;
-
- struct {
bool enabled; // GL_CULL_FACE
GLenum mode; // GL_CULL_FACE_MODE
GLenum front_face; // GL_FRONT_FACE
@@ -176,6 +172,12 @@ public:
GLfloat clamp;
} polygon_offset;
+ struct {
+ bool enabled; // GL_ALPHA_TEST
+ GLenum func; // GL_ALPHA_TEST_FUNC
+ GLfloat ref; // GL_ALPHA_TEST_REF
+ } alpha_test;
+
std::array<bool, 8> clip_distance; // GL_CLIP_DISTANCE
OpenGLState();
@@ -184,34 +186,26 @@ public:
static OpenGLState GetCurState() {
return cur_state;
}
+
static bool GetsRGBUsed() {
return s_rgb_used;
}
+
static void ClearsRGBUsed() {
s_rgb_used = false;
}
+
/// Apply this state as the current OpenGL state
void Apply() const;
- /// Apply only the state affecting the framebuffer
+
void ApplyFramebufferState() const;
- /// Apply only the state affecting the vertex array
void ApplyVertexArrayState() const;
- /// Set the initial OpenGL state
- static void ApplyDefaultState();
- /// Resets any references to the given resource
- OpenGLState& UnbindTexture(GLuint handle);
- OpenGLState& ResetSampler(GLuint handle);
- OpenGLState& ResetProgram(GLuint handle);
- OpenGLState& ResetPipeline(GLuint handle);
- OpenGLState& ResetVertexArray(GLuint handle);
- OpenGLState& ResetFramebuffer(GLuint handle);
- void EmulateViewportWithScissor();
-
-private:
- static OpenGLState cur_state;
- // Workaround for sRGB problems caused by
- // QT not supporting srgb output
- static bool s_rgb_used;
+ void ApplyShaderProgram() const;
+ void ApplyProgramPipeline() const;
+ void ApplyClipDistances() const;
+ void ApplyPointSize() const;
+ void ApplyFragmentColorClamp() const;
+ void ApplyMultisample() const;
void ApplySRgb() const;
void ApplyCulling() const;
void ApplyColorMask() const;
@@ -227,6 +221,27 @@ private:
void ApplySamplers() const;
void ApplyDepthClamp() const;
void ApplyPolygonOffset() const;
+ void ApplyAlphaTest() const;
+
+ /// Set the initial OpenGL state
+ static void ApplyDefaultState();
+
+ /// Resets any references to the given resource
+ OpenGLState& UnbindTexture(GLuint handle);
+ OpenGLState& ResetSampler(GLuint handle);
+ OpenGLState& ResetProgram(GLuint handle);
+ OpenGLState& ResetPipeline(GLuint handle);
+ OpenGLState& ResetVertexArray(GLuint handle);
+ OpenGLState& ResetFramebuffer(GLuint handle);
+
+ /// Viewport does not affects glClearBuffer so emulate viewport using scissor test
+ void EmulateViewportWithScissor();
+
+private:
+ static OpenGLState cur_state;
+
+ // Workaround for sRGB problems caused by QT not supporting srgb output
+ static bool s_rgb_used;
};
} // 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 a8833c06e..ea77dd211 100644
--- a/src/video_core/renderer_opengl/maxwell_to_gl.h
+++ b/src/video_core/renderer_opengl/maxwell_to_gl.h
@@ -27,8 +27,7 @@ using Maxwell = Tegra::Engines::Maxwell3D::Regs;
inline GLenum VertexType(Maxwell::VertexAttribute attrib) {
switch (attrib.type) {
case Maxwell::VertexAttribute::Type::UnsignedInt:
- case Maxwell::VertexAttribute::Type::UnsignedNorm: {
-
+ case Maxwell::VertexAttribute::Type::UnsignedNorm:
switch (attrib.size) {
case Maxwell::VertexAttribute::Size::Size_8:
case Maxwell::VertexAttribute::Size::Size_8_8:
@@ -47,16 +46,13 @@ inline GLenum VertexType(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:
+ LOG_CRITICAL(Render_OpenGL, "Unimplemented vertex size={}", attrib.SizeString());
+ UNREACHABLE();
+ return {};
}
-
- LOG_CRITICAL(Render_OpenGL, "Unimplemented vertex size={}", attrib.SizeString());
- UNREACHABLE();
- return {};
- }
-
case Maxwell::VertexAttribute::Type::SignedInt:
- case Maxwell::VertexAttribute::Type::SignedNorm: {
-
+ case Maxwell::VertexAttribute::Type::SignedNorm:
switch (attrib.size) {
case Maxwell::VertexAttribute::Size::Size_8:
case Maxwell::VertexAttribute::Size::Size_8_8:
@@ -75,14 +71,12 @@ inline GLenum VertexType(Maxwell::VertexAttribute attrib) {
return GL_INT;
case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
return GL_INT_2_10_10_10_REV;
+ default:
+ LOG_CRITICAL(Render_OpenGL, "Unimplemented vertex size={}", attrib.SizeString());
+ UNREACHABLE();
+ return {};
}
-
- LOG_CRITICAL(Render_OpenGL, "Unimplemented vertex size={}", attrib.SizeString());
- UNREACHABLE();
- return {};
- }
-
- case Maxwell::VertexAttribute::Type::Float: {
+ case Maxwell::VertexAttribute::Type::Float:
switch (attrib.size) {
case Maxwell::VertexAttribute::Size::Size_16:
case Maxwell::VertexAttribute::Size::Size_16_16:
@@ -94,13 +88,16 @@ inline GLenum VertexType(Maxwell::VertexAttribute attrib) {
case Maxwell::VertexAttribute::Size::Size_32_32_32:
case Maxwell::VertexAttribute::Size::Size_32_32_32_32:
return GL_FLOAT;
+ default:
+ LOG_CRITICAL(Render_OpenGL, "Unimplemented vertex size={}", attrib.SizeString());
+ UNREACHABLE();
+ return {};
}
+ default:
+ LOG_CRITICAL(Render_OpenGL, "Unimplemented vertex type={}", attrib.TypeString());
+ UNREACHABLE();
+ return {};
}
- }
-
- LOG_CRITICAL(Render_OpenGL, "Unimplemented vertex type={}", attrib.TypeString());
- UNREACHABLE();
- return {};
}
inline GLenum IndexFormat(Maxwell::IndexFormat index_format) {
@@ -129,10 +126,15 @@ inline GLenum PrimitiveTopology(Maxwell::PrimitiveTopology topology) {
return GL_TRIANGLES;
case Maxwell::PrimitiveTopology::TriangleStrip:
return GL_TRIANGLE_STRIP;
+ case Maxwell::PrimitiveTopology::TriangleFan:
+ return GL_TRIANGLE_FAN;
+ case Maxwell::PrimitiveTopology::Quads:
+ return GL_QUADS;
+ default:
+ LOG_CRITICAL(Render_OpenGL, "Unimplemented topology={}", static_cast<u32>(topology));
+ UNREACHABLE();
+ return {};
}
- LOG_CRITICAL(Render_OpenGL, "Unimplemented topology={}", static_cast<u32>(topology));
- UNREACHABLE();
- return {};
}
inline GLenum TextureFilterMode(Tegra::Texture::TextureFilter filter_mode,
@@ -173,11 +175,8 @@ inline GLenum WrapMode(Tegra::Texture::WrapMode wrap_mode) {
return GL_CLAMP_TO_EDGE;
case Tegra::Texture::WrapMode::Border:
return GL_CLAMP_TO_BORDER;
- case Tegra::Texture::WrapMode::ClampOGL:
- // TODO(Subv): GL_CLAMP was removed as of OpenGL 3.1, to implement GL_CLAMP, we can use
- // GL_CLAMP_TO_BORDER to get the border color of the texture, and then sample the edge to
- // manually mix them. However the shader part of this is not yet implemented.
- return GL_CLAMP_TO_BORDER;
+ case Tegra::Texture::WrapMode::Clamp:
+ return GL_CLAMP;
case Tegra::Texture::WrapMode::MirrorOnceClampToEdge:
return GL_MIRROR_CLAMP_TO_EDGE;
case Tegra::Texture::WrapMode::MirrorOnceBorder:
@@ -186,9 +185,10 @@ inline GLenum WrapMode(Tegra::Texture::WrapMode wrap_mode) {
} else {
return GL_MIRROR_CLAMP_TO_EDGE;
}
+ default:
+ LOG_ERROR(Render_OpenGL, "Unimplemented texture wrap mode={}", static_cast<u32>(wrap_mode));
+ return GL_REPEAT;
}
- LOG_ERROR(Render_OpenGL, "Unimplemented texture wrap mode={}", static_cast<u32>(wrap_mode));
- return GL_REPEAT;
}
inline GLenum DepthCompareFunc(Tegra::Texture::DepthCompareFunc func) {
diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp
index 8b510b6ae..3451d321d 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.cpp
+++ b/src/video_core/renderer_opengl/renderer_opengl.cpp
@@ -5,7 +5,6 @@
#include <algorithm>
#include <cstddef>
#include <cstdlib>
-#include <cstring>
#include <memory>
#include <glad/glad.h>
#include "common/assert.h"
@@ -98,8 +97,8 @@ static std::array<GLfloat, 3 * 2> MakeOrthographicMatrix(const float width, cons
return matrix;
}
-RendererOpenGL::RendererOpenGL(Core::Frontend::EmuWindow& window, Core::System& system)
- : VideoCore::RendererBase{window}, system{system} {}
+RendererOpenGL::RendererOpenGL(Core::Frontend::EmuWindow& emu_window, Core::System& system)
+ : VideoCore::RendererBase{emu_window}, emu_window{emu_window}, system{system} {}
RendererOpenGL::~RendererOpenGL() = default;
@@ -164,12 +163,13 @@ void RendererOpenGL::LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuf
// Reset the screen info's display texture to its own permanent texture
screen_info.display_texture = screen_info.texture.resource.handle;
- Memory::RasterizerFlushVirtualRegion(framebuffer_addr, size_in_bytes,
- Memory::FlushMode::Flush);
+ rasterizer->FlushRegion(ToCacheAddr(Memory::GetPointer(framebuffer_addr)), size_in_bytes);
- VideoCore::MortonCopyPixels128(framebuffer.width, framebuffer.height, bytes_per_pixel, 4,
- Memory::GetPointer(framebuffer_addr),
- gl_framebuffer_data.data(), true);
+ constexpr u32 linear_bpp = 4;
+ VideoCore::MortonCopyPixels128(VideoCore::MortonSwizzleMode::MortonToLinear,
+ framebuffer.width, framebuffer.height, bytes_per_pixel,
+ linear_bpp, Memory::GetPointer(framebuffer_addr),
+ gl_framebuffer_data.data());
glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(framebuffer.stride));
@@ -265,7 +265,7 @@ void RendererOpenGL::CreateRasterizer() {
}
// Initialize sRGB Usage
OpenGLState::ClearsRGBUsed();
- rasterizer = std::make_unique<RasterizerOpenGL>(render_window, system, screen_info);
+ rasterizer = std::make_unique<RasterizerOpenGL>(system, emu_window, screen_info);
}
void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture,
diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h
index 6cbf9d2cb..4aebf2321 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.h
+++ b/src/video_core/renderer_opengl/renderer_opengl.h
@@ -45,7 +45,7 @@ struct ScreenInfo {
class RendererOpenGL : public VideoCore::RendererBase {
public:
- explicit RendererOpenGL(Core::Frontend::EmuWindow& window, Core::System& system);
+ explicit RendererOpenGL(Core::Frontend::EmuWindow& emu_window, Core::System& system);
~RendererOpenGL() override;
/// Swap buffers (render frame)
@@ -77,6 +77,7 @@ private:
void LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b, u8 color_a,
const TextureInfo& texture);
+ Core::Frontend::EmuWindow& emu_window;
Core::System& system;
OpenGLState state;
diff --git a/src/video_core/renderer_opengl/utils.cpp b/src/video_core/renderer_opengl/utils.cpp
index d84634cb3..f23fc9f9d 100644
--- a/src/video_core/renderer_opengl/utils.cpp
+++ b/src/video_core/renderer_opengl/utils.cpp
@@ -5,32 +5,60 @@
#include <string>
#include <fmt/format.h>
#include <glad/glad.h>
+#include "common/assert.h"
#include "common/common_types.h"
#include "video_core/renderer_opengl/utils.h"
namespace OpenGL {
-void LabelGLObject(GLenum identifier, GLuint handle, VAddr addr, std::string extra_info) {
+BindBuffersRangePushBuffer::BindBuffersRangePushBuffer(GLenum target) : target{target} {}
+
+BindBuffersRangePushBuffer::~BindBuffersRangePushBuffer() = default;
+
+void BindBuffersRangePushBuffer::Setup(GLuint first_) {
+ first = first_;
+ buffers.clear();
+ offsets.clear();
+ sizes.clear();
+}
+
+void BindBuffersRangePushBuffer::Push(GLuint buffer, GLintptr offset, GLsizeiptr size) {
+ buffers.push_back(buffer);
+ offsets.push_back(offset);
+ sizes.push_back(size);
+}
+
+void BindBuffersRangePushBuffer::Bind() const {
+ const std::size_t count{buffers.size()};
+ DEBUG_ASSERT(count == offsets.size() && count == sizes.size());
+ if (count == 0) {
+ return;
+ }
+ glBindBuffersRange(target, first, static_cast<GLsizei>(count), buffers.data(), offsets.data(),
+ sizes.data());
+}
+
+void LabelGLObject(GLenum identifier, GLuint handle, VAddr addr, std::string_view extra_info) {
if (!GLAD_GL_KHR_debug) {
- return; // We don't need to throw an error as this is just for debugging
+ // We don't need to throw an error as this is just for debugging
+ return;
}
- const std::string nice_addr = fmt::format("0x{:016x}", addr);
- std::string object_label;
+ std::string object_label;
if (extra_info.empty()) {
switch (identifier) {
case GL_TEXTURE:
- object_label = "Texture@" + nice_addr;
+ object_label = fmt::format("Texture@0x{:016X}", addr);
break;
case GL_PROGRAM:
- object_label = "Shader@" + nice_addr;
+ object_label = fmt::format("Shader@0x{:016X}", addr);
break;
default:
- object_label = fmt::format("Object(0x{:x})@{}", identifier, nice_addr);
+ object_label = fmt::format("Object(0x{:X})@0x{:016X}", identifier, addr);
break;
}
} else {
- object_label = extra_info + '@' + nice_addr;
+ object_label = fmt::format("{}@0x{:016X}", extra_info, addr);
}
glObjectLabel(identifier, handle, -1, static_cast<const GLchar*>(object_label.c_str()));
}
diff --git a/src/video_core/renderer_opengl/utils.h b/src/video_core/renderer_opengl/utils.h
index 1fcb6fc11..b3e9fc499 100644
--- a/src/video_core/renderer_opengl/utils.h
+++ b/src/video_core/renderer_opengl/utils.h
@@ -4,12 +4,32 @@
#pragma once
-#include <string>
+#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 extra_info = "");
+class BindBuffersRangePushBuffer {
+public:
+ BindBuffersRangePushBuffer(GLenum target);
+ ~BindBuffersRangePushBuffer();
+
+ void Setup(GLuint first_);
+
+ void Push(GLuint buffer, GLintptr offset, GLsizeiptr size);
+
+ void Bind() const;
+
+private:
+ GLenum target;
+ GLuint first;
+ std::vector<GLuint> buffers;
+ std::vector<GLintptr> offsets;
+ std::vector<GLsizeiptr> sizes;
+};
+
+void LabelGLObject(GLenum identifier, GLuint handle, VAddr addr, std::string_view extra_info = {});
} // namespace OpenGL \ No newline at end of file
diff --git a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
new file mode 100644
index 000000000..0bbbf6851
--- /dev/null
+++ b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
@@ -0,0 +1,485 @@
+// Copyright 2019 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 "common/logging/log.h"
+#include "video_core/engines/maxwell_3d.h"
+#include "video_core/renderer_vulkan/declarations.h"
+#include "video_core/renderer_vulkan/maxwell_to_vk.h"
+#include "video_core/renderer_vulkan/vk_device.h"
+#include "video_core/surface.h"
+
+namespace Vulkan::MaxwellToVK {
+
+namespace Sampler {
+
+vk::Filter Filter(Tegra::Texture::TextureFilter filter) {
+ switch (filter) {
+ case Tegra::Texture::TextureFilter::Linear:
+ return vk::Filter::eLinear;
+ case Tegra::Texture::TextureFilter::Nearest:
+ return vk::Filter::eNearest;
+ }
+ UNIMPLEMENTED_MSG("Unimplemented sampler filter={}", static_cast<u32>(filter));
+ return {};
+}
+
+vk::SamplerMipmapMode MipmapMode(Tegra::Texture::TextureMipmapFilter mipmap_filter) {
+ switch (mipmap_filter) {
+ case Tegra::Texture::TextureMipmapFilter::None:
+ // TODO(Rodrigo): None seems to be mapped to OpenGL's mag and min filters without mipmapping
+ // (e.g. GL_NEAREST and GL_LINEAR). Vulkan doesn't have such a thing, find out if we have to
+ // use an image view with a single mipmap level to emulate this.
+ return vk::SamplerMipmapMode::eLinear;
+ case Tegra::Texture::TextureMipmapFilter::Linear:
+ return vk::SamplerMipmapMode::eLinear;
+ case Tegra::Texture::TextureMipmapFilter::Nearest:
+ return vk::SamplerMipmapMode::eNearest;
+ }
+ UNIMPLEMENTED_MSG("Unimplemented sampler mipmap mode={}", static_cast<u32>(mipmap_filter));
+ return {};
+}
+
+vk::SamplerAddressMode WrapMode(Tegra::Texture::WrapMode wrap_mode) {
+ switch (wrap_mode) {
+ case Tegra::Texture::WrapMode::Wrap:
+ return vk::SamplerAddressMode::eRepeat;
+ case Tegra::Texture::WrapMode::Mirror:
+ return vk::SamplerAddressMode::eMirroredRepeat;
+ case Tegra::Texture::WrapMode::ClampToEdge:
+ return vk::SamplerAddressMode::eClampToEdge;
+ case Tegra::Texture::WrapMode::Border:
+ return vk::SamplerAddressMode::eClampToBorder;
+ case Tegra::Texture::WrapMode::Clamp:
+ // TODO(Rodrigo): GL_CLAMP was removed as of OpenGL 3.1, to implement GL_CLAMP, we can use
+ // eClampToBorder to get the border color of the texture, and then sample the edge to
+ // manually mix them. However the shader part of this is not yet implemented.
+ return vk::SamplerAddressMode::eClampToBorder;
+ case Tegra::Texture::WrapMode::MirrorOnceClampToEdge:
+ return vk::SamplerAddressMode::eMirrorClampToEdge;
+ case Tegra::Texture::WrapMode::MirrorOnceBorder:
+ UNIMPLEMENTED();
+ return vk::SamplerAddressMode::eMirrorClampToEdge;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented wrap mode={}", static_cast<u32>(wrap_mode));
+ return {};
+ }
+}
+
+vk::CompareOp DepthCompareFunction(Tegra::Texture::DepthCompareFunc depth_compare_func) {
+ switch (depth_compare_func) {
+ case Tegra::Texture::DepthCompareFunc::Never:
+ return vk::CompareOp::eNever;
+ case Tegra::Texture::DepthCompareFunc::Less:
+ return vk::CompareOp::eLess;
+ case Tegra::Texture::DepthCompareFunc::LessEqual:
+ return vk::CompareOp::eLessOrEqual;
+ case Tegra::Texture::DepthCompareFunc::Equal:
+ return vk::CompareOp::eEqual;
+ case Tegra::Texture::DepthCompareFunc::NotEqual:
+ return vk::CompareOp::eNotEqual;
+ case Tegra::Texture::DepthCompareFunc::Greater:
+ return vk::CompareOp::eGreater;
+ case Tegra::Texture::DepthCompareFunc::GreaterEqual:
+ return vk::CompareOp::eGreaterOrEqual;
+ case Tegra::Texture::DepthCompareFunc::Always:
+ return vk::CompareOp::eAlways;
+ }
+ UNIMPLEMENTED_MSG("Unimplemented sampler depth compare function={}",
+ static_cast<u32>(depth_compare_func));
+ return {};
+}
+
+} // namespace Sampler
+
+struct FormatTuple {
+ vk::Format format; ///< Vulkan format
+ ComponentType component_type; ///< Abstracted component type
+ bool attachable; ///< True when this format can be used as an attachment
+};
+
+static constexpr std::array<FormatTuple, VideoCore::Surface::MaxPixelFormat> tex_format_tuples = {{
+ {vk::Format::eA8B8G8R8UnormPack32, ComponentType::UNorm, true}, // ABGR8U
+ {vk::Format::eUndefined, ComponentType::Invalid, false}, // ABGR8S
+ {vk::Format::eUndefined, ComponentType::Invalid, false}, // ABGR8UI
+ {vk::Format::eB5G6R5UnormPack16, ComponentType::UNorm, false}, // B5G6R5U
+ {vk::Format::eA2B10G10R10UnormPack32, ComponentType::UNorm, true}, // A2B10G10R10U
+ {vk::Format::eUndefined, ComponentType::Invalid, false}, // A1B5G5R5U
+ {vk::Format::eR8Unorm, ComponentType::UNorm, true}, // R8U
+ {vk::Format::eUndefined, ComponentType::Invalid, false}, // R8UI
+ {vk::Format::eUndefined, ComponentType::Invalid, false}, // RGBA16F
+ {vk::Format::eUndefined, ComponentType::Invalid, false}, // RGBA16U
+ {vk::Format::eUndefined, ComponentType::Invalid, false}, // RGBA16UI
+ {vk::Format::eUndefined, ComponentType::Invalid, false}, // R11FG11FB10F
+ {vk::Format::eUndefined, ComponentType::Invalid, false}, // RGBA32UI
+ {vk::Format::eBc1RgbaUnormBlock, ComponentType::UNorm, false}, // DXT1
+ {vk::Format::eBc2UnormBlock, ComponentType::UNorm, false}, // DXT23
+ {vk::Format::eBc3UnormBlock, ComponentType::UNorm, false}, // DXT45
+ {vk::Format::eBc4UnormBlock, ComponentType::UNorm, false}, // DXN1
+ {vk::Format::eUndefined, ComponentType::Invalid, false}, // DXN2UNORM
+ {vk::Format::eUndefined, ComponentType::Invalid, false}, // DXN2SNORM
+ {vk::Format::eUndefined, ComponentType::Invalid, false}, // BC7U
+ {vk::Format::eUndefined, ComponentType::Invalid, false}, // BC6H_UF16
+ {vk::Format::eUndefined, ComponentType::Invalid, false}, // BC6H_SF16
+ {vk::Format::eUndefined, ComponentType::Invalid, false}, // ASTC_2D_4X4
+ {vk::Format::eUndefined, ComponentType::Invalid, false}, // BGRA8
+ {vk::Format::eUndefined, ComponentType::Invalid, false}, // RGBA32F
+ {vk::Format::eUndefined, ComponentType::Invalid, false}, // RG32F
+ {vk::Format::eUndefined, ComponentType::Invalid, false}, // R32F
+ {vk::Format::eUndefined, ComponentType::Invalid, false}, // R16F
+ {vk::Format::eUndefined, ComponentType::Invalid, false}, // R16U
+ {vk::Format::eUndefined, ComponentType::Invalid, false}, // R16S
+ {vk::Format::eUndefined, ComponentType::Invalid, false}, // R16UI
+ {vk::Format::eUndefined, ComponentType::Invalid, false}, // R16I
+ {vk::Format::eUndefined, ComponentType::Invalid, false}, // RG16
+ {vk::Format::eUndefined, ComponentType::Invalid, false}, // RG16F
+ {vk::Format::eUndefined, ComponentType::Invalid, false}, // RG16UI
+ {vk::Format::eUndefined, ComponentType::Invalid, false}, // RG16I
+ {vk::Format::eUndefined, ComponentType::Invalid, false}, // RG16S
+ {vk::Format::eUndefined, ComponentType::Invalid, false}, // RGB32F
+ {vk::Format::eA8B8G8R8SrgbPack32, ComponentType::UNorm, true}, // RGBA8_SRGB
+ {vk::Format::eUndefined, ComponentType::Invalid, false}, // RG8U
+ {vk::Format::eUndefined, ComponentType::Invalid, false}, // RG8S
+ {vk::Format::eUndefined, ComponentType::Invalid, false}, // RG32UI
+ {vk::Format::eUndefined, ComponentType::Invalid, false}, // R32UI
+ {vk::Format::eUndefined, ComponentType::Invalid, false}, // ASTC_2D_8X8
+ {vk::Format::eUndefined, ComponentType::Invalid, false}, // ASTC_2D_8X5
+ {vk::Format::eUndefined, ComponentType::Invalid, false}, // ASTC_2D_5X4
+
+ // Compressed sRGB formats
+ {vk::Format::eUndefined, ComponentType::Invalid, false}, // BGRA8_SRGB
+ {vk::Format::eUndefined, ComponentType::Invalid, false}, // DXT1_SRGB
+ {vk::Format::eUndefined, ComponentType::Invalid, false}, // DXT23_SRGB
+ {vk::Format::eUndefined, ComponentType::Invalid, false}, // DXT45_SRGB
+ {vk::Format::eUndefined, ComponentType::Invalid, false}, // BC7U_SRGB
+ {vk::Format::eUndefined, ComponentType::Invalid, false}, // ASTC_2D_4X4_SRGB
+ {vk::Format::eUndefined, ComponentType::Invalid, false}, // ASTC_2D_8X8_SRGB
+ {vk::Format::eUndefined, ComponentType::Invalid, false}, // ASTC_2D_8X5_SRGB
+ {vk::Format::eUndefined, ComponentType::Invalid, false}, // ASTC_2D_5X4_SRGB
+ {vk::Format::eUndefined, ComponentType::Invalid, false}, // ASTC_2D_5X5
+ {vk::Format::eUndefined, ComponentType::Invalid, false}, // ASTC_2D_5X5_SRGB
+ {vk::Format::eUndefined, ComponentType::Invalid, false}, // ASTC_2D_10X8
+ {vk::Format::eUndefined, ComponentType::Invalid, false}, // ASTC_2D_10X8_SRGB
+
+ // Depth formats
+ {vk::Format::eD32Sfloat, ComponentType::Float, true}, // Z32F
+ {vk::Format::eD16Unorm, ComponentType::UNorm, true}, // Z16
+
+ // DepthStencil formats
+ {vk::Format::eD24UnormS8Uint, ComponentType::UNorm, true}, // Z24S8
+ {vk::Format::eD24UnormS8Uint, ComponentType::UNorm, true}, // S8Z24 (emulated)
+ {vk::Format::eUndefined, ComponentType::Invalid, false}, // Z32FS8
+}};
+
+static constexpr bool IsZetaFormat(PixelFormat pixel_format) {
+ return pixel_format >= PixelFormat::MaxColorFormat &&
+ pixel_format < PixelFormat::MaxDepthStencilFormat;
+}
+
+std::pair<vk::Format, bool> SurfaceFormat(const VKDevice& device, FormatType format_type,
+ PixelFormat pixel_format, ComponentType component_type) {
+ ASSERT(static_cast<std::size_t>(pixel_format) < tex_format_tuples.size());
+
+ const auto tuple = tex_format_tuples[static_cast<u32>(pixel_format)];
+ UNIMPLEMENTED_IF_MSG(tuple.format == vk::Format::eUndefined,
+ "Unimplemented texture format with pixel format={} and component type={}",
+ static_cast<u32>(pixel_format), static_cast<u32>(component_type));
+ ASSERT_MSG(component_type == tuple.component_type, "Component type mismatch");
+
+ auto usage = vk::FormatFeatureFlagBits::eSampledImage |
+ vk::FormatFeatureFlagBits::eTransferDst | vk::FormatFeatureFlagBits::eTransferSrc;
+ if (tuple.attachable) {
+ usage |= IsZetaFormat(pixel_format) ? vk::FormatFeatureFlagBits::eDepthStencilAttachment
+ : vk::FormatFeatureFlagBits::eColorAttachment;
+ }
+ return {device.GetSupportedFormat(tuple.format, usage, format_type), tuple.attachable};
+}
+
+vk::ShaderStageFlagBits ShaderStage(Maxwell::ShaderStage stage) {
+ switch (stage) {
+ case Maxwell::ShaderStage::Vertex:
+ return vk::ShaderStageFlagBits::eVertex;
+ case Maxwell::ShaderStage::TesselationControl:
+ return vk::ShaderStageFlagBits::eTessellationControl;
+ case Maxwell::ShaderStage::TesselationEval:
+ return vk::ShaderStageFlagBits::eTessellationEvaluation;
+ case Maxwell::ShaderStage::Geometry:
+ return vk::ShaderStageFlagBits::eGeometry;
+ case Maxwell::ShaderStage::Fragment:
+ return vk::ShaderStageFlagBits::eFragment;
+ }
+ UNIMPLEMENTED_MSG("Unimplemented shader stage={}", static_cast<u32>(stage));
+ return {};
+}
+
+vk::PrimitiveTopology PrimitiveTopology(Maxwell::PrimitiveTopology topology) {
+ switch (topology) {
+ case Maxwell::PrimitiveTopology::Points:
+ return vk::PrimitiveTopology::ePointList;
+ case Maxwell::PrimitiveTopology::Lines:
+ return vk::PrimitiveTopology::eLineList;
+ case Maxwell::PrimitiveTopology::LineStrip:
+ return vk::PrimitiveTopology::eLineStrip;
+ case Maxwell::PrimitiveTopology::Triangles:
+ return vk::PrimitiveTopology::eTriangleList;
+ case Maxwell::PrimitiveTopology::TriangleStrip:
+ return vk::PrimitiveTopology::eTriangleStrip;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented topology={}", static_cast<u32>(topology));
+ return {};
+ }
+}
+
+vk::Format VertexFormat(Maxwell::VertexAttribute::Type type, Maxwell::VertexAttribute::Size size) {
+ switch (type) {
+ case Maxwell::VertexAttribute::Type::SignedNorm:
+ break;
+ case Maxwell::VertexAttribute::Type::UnsignedNorm:
+ switch (size) {
+ case Maxwell::VertexAttribute::Size::Size_8_8_8_8:
+ return vk::Format::eR8G8B8A8Unorm;
+ default:
+ break;
+ }
+ break;
+ case Maxwell::VertexAttribute::Type::SignedInt:
+ break;
+ case Maxwell::VertexAttribute::Type::UnsignedInt:
+ switch (size) {
+ case Maxwell::VertexAttribute::Size::Size_32:
+ return vk::Format::eR32Uint;
+ default:
+ break;
+ }
+ case Maxwell::VertexAttribute::Type::UnsignedScaled:
+ case Maxwell::VertexAttribute::Type::SignedScaled:
+ break;
+ case Maxwell::VertexAttribute::Type::Float:
+ switch (size) {
+ case Maxwell::VertexAttribute::Size::Size_32_32_32_32:
+ return vk::Format::eR32G32B32A32Sfloat;
+ case Maxwell::VertexAttribute::Size::Size_32_32_32:
+ return vk::Format::eR32G32B32Sfloat;
+ case Maxwell::VertexAttribute::Size::Size_32_32:
+ return vk::Format::eR32G32Sfloat;
+ case Maxwell::VertexAttribute::Size::Size_32:
+ return vk::Format::eR32Sfloat;
+ default:
+ break;
+ }
+ break;
+ }
+ UNIMPLEMENTED_MSG("Unimplemented vertex format of type={} and size={}", static_cast<u32>(type),
+ static_cast<u32>(size));
+ return {};
+}
+
+vk::CompareOp ComparisonOp(Maxwell::ComparisonOp comparison) {
+ switch (comparison) {
+ case Maxwell::ComparisonOp::Never:
+ case Maxwell::ComparisonOp::NeverOld:
+ return vk::CompareOp::eNever;
+ case Maxwell::ComparisonOp::Less:
+ case Maxwell::ComparisonOp::LessOld:
+ return vk::CompareOp::eLess;
+ case Maxwell::ComparisonOp::Equal:
+ case Maxwell::ComparisonOp::EqualOld:
+ return vk::CompareOp::eEqual;
+ case Maxwell::ComparisonOp::LessEqual:
+ case Maxwell::ComparisonOp::LessEqualOld:
+ return vk::CompareOp::eLessOrEqual;
+ case Maxwell::ComparisonOp::Greater:
+ case Maxwell::ComparisonOp::GreaterOld:
+ return vk::CompareOp::eGreater;
+ case Maxwell::ComparisonOp::NotEqual:
+ case Maxwell::ComparisonOp::NotEqualOld:
+ return vk::CompareOp::eNotEqual;
+ case Maxwell::ComparisonOp::GreaterEqual:
+ case Maxwell::ComparisonOp::GreaterEqualOld:
+ return vk::CompareOp::eGreaterOrEqual;
+ case Maxwell::ComparisonOp::Always:
+ case Maxwell::ComparisonOp::AlwaysOld:
+ return vk::CompareOp::eAlways;
+ }
+ UNIMPLEMENTED_MSG("Unimplemented comparison op={}", static_cast<u32>(comparison));
+ return {};
+}
+
+vk::IndexType IndexFormat(Maxwell::IndexFormat index_format) {
+ switch (index_format) {
+ case Maxwell::IndexFormat::UnsignedByte:
+ UNIMPLEMENTED_MSG("Vulkan does not support native u8 index format");
+ return vk::IndexType::eUint16;
+ case Maxwell::IndexFormat::UnsignedShort:
+ return vk::IndexType::eUint16;
+ case Maxwell::IndexFormat::UnsignedInt:
+ return vk::IndexType::eUint32;
+ }
+ UNIMPLEMENTED_MSG("Unimplemented index_format={}", static_cast<u32>(index_format));
+ return {};
+}
+
+vk::StencilOp StencilOp(Maxwell::StencilOp stencil_op) {
+ switch (stencil_op) {
+ case Maxwell::StencilOp::Keep:
+ case Maxwell::StencilOp::KeepOGL:
+ return vk::StencilOp::eKeep;
+ case Maxwell::StencilOp::Zero:
+ case Maxwell::StencilOp::ZeroOGL:
+ return vk::StencilOp::eZero;
+ case Maxwell::StencilOp::Replace:
+ case Maxwell::StencilOp::ReplaceOGL:
+ return vk::StencilOp::eReplace;
+ case Maxwell::StencilOp::Incr:
+ case Maxwell::StencilOp::IncrOGL:
+ return vk::StencilOp::eIncrementAndClamp;
+ case Maxwell::StencilOp::Decr:
+ case Maxwell::StencilOp::DecrOGL:
+ return vk::StencilOp::eDecrementAndClamp;
+ case Maxwell::StencilOp::Invert:
+ case Maxwell::StencilOp::InvertOGL:
+ return vk::StencilOp::eInvert;
+ case Maxwell::StencilOp::IncrWrap:
+ case Maxwell::StencilOp::IncrWrapOGL:
+ return vk::StencilOp::eIncrementAndWrap;
+ case Maxwell::StencilOp::DecrWrap:
+ case Maxwell::StencilOp::DecrWrapOGL:
+ return vk::StencilOp::eDecrementAndWrap;
+ }
+ UNIMPLEMENTED_MSG("Unimplemented stencil op={}", static_cast<u32>(stencil_op));
+ return {};
+}
+
+vk::BlendOp BlendEquation(Maxwell::Blend::Equation equation) {
+ switch (equation) {
+ case Maxwell::Blend::Equation::Add:
+ case Maxwell::Blend::Equation::AddGL:
+ return vk::BlendOp::eAdd;
+ case Maxwell::Blend::Equation::Subtract:
+ case Maxwell::Blend::Equation::SubtractGL:
+ return vk::BlendOp::eSubtract;
+ case Maxwell::Blend::Equation::ReverseSubtract:
+ case Maxwell::Blend::Equation::ReverseSubtractGL:
+ return vk::BlendOp::eReverseSubtract;
+ case Maxwell::Blend::Equation::Min:
+ case Maxwell::Blend::Equation::MinGL:
+ return vk::BlendOp::eMin;
+ case Maxwell::Blend::Equation::Max:
+ case Maxwell::Blend::Equation::MaxGL:
+ return vk::BlendOp::eMax;
+ }
+ UNIMPLEMENTED_MSG("Unimplemented blend equation={}", static_cast<u32>(equation));
+ return {};
+}
+
+vk::BlendFactor BlendFactor(Maxwell::Blend::Factor factor) {
+ switch (factor) {
+ case Maxwell::Blend::Factor::Zero:
+ case Maxwell::Blend::Factor::ZeroGL:
+ return vk::BlendFactor::eZero;
+ case Maxwell::Blend::Factor::One:
+ case Maxwell::Blend::Factor::OneGL:
+ return vk::BlendFactor::eOne;
+ case Maxwell::Blend::Factor::SourceColor:
+ case Maxwell::Blend::Factor::SourceColorGL:
+ return vk::BlendFactor::eSrcColor;
+ case Maxwell::Blend::Factor::OneMinusSourceColor:
+ case Maxwell::Blend::Factor::OneMinusSourceColorGL:
+ return vk::BlendFactor::eOneMinusSrcColor;
+ case Maxwell::Blend::Factor::SourceAlpha:
+ case Maxwell::Blend::Factor::SourceAlphaGL:
+ return vk::BlendFactor::eSrcAlpha;
+ case Maxwell::Blend::Factor::OneMinusSourceAlpha:
+ case Maxwell::Blend::Factor::OneMinusSourceAlphaGL:
+ return vk::BlendFactor::eOneMinusSrcAlpha;
+ case Maxwell::Blend::Factor::DestAlpha:
+ case Maxwell::Blend::Factor::DestAlphaGL:
+ return vk::BlendFactor::eDstAlpha;
+ case Maxwell::Blend::Factor::OneMinusDestAlpha:
+ case Maxwell::Blend::Factor::OneMinusDestAlphaGL:
+ return vk::BlendFactor::eOneMinusDstAlpha;
+ case Maxwell::Blend::Factor::DestColor:
+ case Maxwell::Blend::Factor::DestColorGL:
+ return vk::BlendFactor::eDstColor;
+ case Maxwell::Blend::Factor::OneMinusDestColor:
+ case Maxwell::Blend::Factor::OneMinusDestColorGL:
+ return vk::BlendFactor::eOneMinusDstColor;
+ case Maxwell::Blend::Factor::SourceAlphaSaturate:
+ case Maxwell::Blend::Factor::SourceAlphaSaturateGL:
+ return vk::BlendFactor::eSrcAlphaSaturate;
+ case Maxwell::Blend::Factor::Source1Color:
+ case Maxwell::Blend::Factor::Source1ColorGL:
+ return vk::BlendFactor::eSrc1Color;
+ case Maxwell::Blend::Factor::OneMinusSource1Color:
+ case Maxwell::Blend::Factor::OneMinusSource1ColorGL:
+ return vk::BlendFactor::eOneMinusSrc1Color;
+ case Maxwell::Blend::Factor::Source1Alpha:
+ case Maxwell::Blend::Factor::Source1AlphaGL:
+ return vk::BlendFactor::eSrc1Alpha;
+ case Maxwell::Blend::Factor::OneMinusSource1Alpha:
+ case Maxwell::Blend::Factor::OneMinusSource1AlphaGL:
+ return vk::BlendFactor::eOneMinusSrc1Alpha;
+ case Maxwell::Blend::Factor::ConstantColor:
+ case Maxwell::Blend::Factor::ConstantColorGL:
+ return vk::BlendFactor::eConstantColor;
+ case Maxwell::Blend::Factor::OneMinusConstantColor:
+ case Maxwell::Blend::Factor::OneMinusConstantColorGL:
+ return vk::BlendFactor::eOneMinusConstantColor;
+ case Maxwell::Blend::Factor::ConstantAlpha:
+ case Maxwell::Blend::Factor::ConstantAlphaGL:
+ return vk::BlendFactor::eConstantAlpha;
+ case Maxwell::Blend::Factor::OneMinusConstantAlpha:
+ case Maxwell::Blend::Factor::OneMinusConstantAlphaGL:
+ return vk::BlendFactor::eOneMinusConstantAlpha;
+ }
+ UNIMPLEMENTED_MSG("Unimplemented blend factor={}", static_cast<u32>(factor));
+ return {};
+}
+
+vk::FrontFace FrontFace(Maxwell::Cull::FrontFace front_face) {
+ switch (front_face) {
+ case Maxwell::Cull::FrontFace::ClockWise:
+ return vk::FrontFace::eClockwise;
+ case Maxwell::Cull::FrontFace::CounterClockWise:
+ return vk::FrontFace::eCounterClockwise;
+ }
+ UNIMPLEMENTED_MSG("Unimplemented front face={}", static_cast<u32>(front_face));
+ return {};
+}
+
+vk::CullModeFlags CullFace(Maxwell::Cull::CullFace cull_face) {
+ switch (cull_face) {
+ case Maxwell::Cull::CullFace::Front:
+ return vk::CullModeFlagBits::eFront;
+ case Maxwell::Cull::CullFace::Back:
+ return vk::CullModeFlagBits::eBack;
+ case Maxwell::Cull::CullFace::FrontAndBack:
+ return vk::CullModeFlagBits::eFrontAndBack;
+ }
+ UNIMPLEMENTED_MSG("Unimplemented cull face={}", static_cast<u32>(cull_face));
+ return {};
+}
+
+vk::ComponentSwizzle SwizzleSource(Tegra::Texture::SwizzleSource swizzle) {
+ switch (swizzle) {
+ case Tegra::Texture::SwizzleSource::Zero:
+ return vk::ComponentSwizzle::eZero;
+ case Tegra::Texture::SwizzleSource::R:
+ return vk::ComponentSwizzle::eR;
+ case Tegra::Texture::SwizzleSource::G:
+ return vk::ComponentSwizzle::eG;
+ case Tegra::Texture::SwizzleSource::B:
+ return vk::ComponentSwizzle::eB;
+ case Tegra::Texture::SwizzleSource::A:
+ return vk::ComponentSwizzle::eA;
+ case Tegra::Texture::SwizzleSource::OneInt:
+ case Tegra::Texture::SwizzleSource::OneFloat:
+ return vk::ComponentSwizzle::eOne;
+ }
+ UNIMPLEMENTED_MSG("Unimplemented swizzle source={}", static_cast<u32>(swizzle));
+ return {};
+}
+
+} // 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
new file mode 100644
index 000000000..4cadc0721
--- /dev/null
+++ b/src/video_core/renderer_vulkan/maxwell_to_vk.h
@@ -0,0 +1,58 @@
+// 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/common_types.h"
+#include "video_core/engines/maxwell_3d.h"
+#include "video_core/renderer_vulkan/declarations.h"
+#include "video_core/renderer_vulkan/vk_device.h"
+#include "video_core/surface.h"
+#include "video_core/textures/texture.h"
+
+namespace Vulkan::MaxwellToVK {
+
+using Maxwell = Tegra::Engines::Maxwell3D::Regs;
+using PixelFormat = VideoCore::Surface::PixelFormat;
+using ComponentType = VideoCore::Surface::ComponentType;
+
+namespace Sampler {
+
+vk::Filter Filter(Tegra::Texture::TextureFilter filter);
+
+vk::SamplerMipmapMode MipmapMode(Tegra::Texture::TextureMipmapFilter mipmap_filter);
+
+vk::SamplerAddressMode WrapMode(Tegra::Texture::WrapMode wrap_mode);
+
+vk::CompareOp DepthCompareFunction(Tegra::Texture::DepthCompareFunc depth_compare_func);
+
+} // namespace Sampler
+
+std::pair<vk::Format, bool> SurfaceFormat(const VKDevice& device, FormatType format_type,
+ PixelFormat pixel_format, ComponentType component_type);
+
+vk::ShaderStageFlagBits ShaderStage(Maxwell::ShaderStage stage);
+
+vk::PrimitiveTopology PrimitiveTopology(Maxwell::PrimitiveTopology topology);
+
+vk::Format VertexFormat(Maxwell::VertexAttribute::Type type, Maxwell::VertexAttribute::Size size);
+
+vk::CompareOp ComparisonOp(Maxwell::ComparisonOp comparison);
+
+vk::IndexType IndexFormat(Maxwell::IndexFormat index_format);
+
+vk::StencilOp StencilOp(Maxwell::StencilOp stencil_op);
+
+vk::BlendOp BlendEquation(Maxwell::Blend::Equation equation);
+
+vk::BlendFactor BlendFactor(Maxwell::Blend::Factor factor);
+
+vk::FrontFace FrontFace(Maxwell::Cull::FrontFace front_face);
+
+vk::CullModeFlags CullFace(Maxwell::Cull::CullFace cull_face);
+
+vk::ComponentSwizzle SwizzleSource(Tegra::Texture::SwizzleSource swizzle);
+
+} // namespace Vulkan::MaxwellToVK
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
index 4a33a6c84..02a9f5ecb 100644
--- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
@@ -10,6 +10,7 @@
#include "common/alignment.h"
#include "common/assert.h"
#include "core/memory.h"
+#include "video_core/memory_manager.h"
#include "video_core/renderer_vulkan/declarations.h"
#include "video_core/renderer_vulkan/vk_buffer_cache.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
@@ -17,6 +18,11 @@
namespace Vulkan {
+CachedBufferEntry::CachedBufferEntry(VAddr cpu_addr, std::size_t size, u64 offset,
+ std::size_t alignment, u8* host_ptr)
+ : RasterizerCacheObject{host_ptr}, cpu_addr{cpu_addr}, size{size}, offset{offset},
+ alignment{alignment} {}
+
VKBufferCache::VKBufferCache(Tegra::MemoryManager& tegra_memory_manager,
VideoCore::RasterizerInterface& rasterizer, const VKDevice& device,
VKMemoryManager& memory_manager, VKScheduler& scheduler, u64 size)
@@ -34,19 +40,20 @@ VKBufferCache::VKBufferCache(Tegra::MemoryManager& tegra_memory_manager,
VKBufferCache::~VKBufferCache() = default;
-u64 VKBufferCache::UploadMemory(Tegra::GPUVAddr gpu_addr, std::size_t size, u64 alignment,
- bool cache) {
+u64 VKBufferCache::UploadMemory(GPUVAddr gpu_addr, std::size_t size, u64 alignment, bool cache) {
const auto cpu_addr{tegra_memory_manager.GpuToCpuAddress(gpu_addr)};
- ASSERT(cpu_addr);
+ ASSERT_MSG(cpu_addr, "Invalid GPU address");
// 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.
cache &= size >= 2048;
+ const auto& host_ptr{Memory::GetPointer(*cpu_addr)};
if (cache) {
- if (auto entry = TryGet(*cpu_addr); entry) {
- if (entry->size >= size && entry->alignment == alignment) {
- return entry->offset;
+ auto entry = TryGet(host_ptr);
+ if (entry) {
+ if (entry->GetSize() >= size && entry->GetAlignment() == alignment) {
+ return entry->GetOffset();
}
Unregister(entry);
}
@@ -55,17 +62,17 @@ u64 VKBufferCache::UploadMemory(Tegra::GPUVAddr gpu_addr, std::size_t size, u64
AlignBuffer(alignment);
const u64 uploaded_offset = buffer_offset;
- Memory::ReadBlock(*cpu_addr, buffer_ptr, size);
+ if (!host_ptr) {
+ return uploaded_offset;
+ }
+ std::memcpy(buffer_ptr, host_ptr, size);
buffer_ptr += size;
buffer_offset += size;
if (cache) {
- auto entry = std::make_shared<CachedBufferEntry>();
- entry->offset = uploaded_offset;
- entry->size = size;
- entry->alignment = alignment;
- entry->addr = *cpu_addr;
+ auto entry = std::make_shared<CachedBufferEntry>(*cpu_addr, size, uploaded_offset,
+ alignment, host_ptr);
Register(entry);
}
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.h b/src/video_core/renderer_vulkan/vk_buffer_cache.h
index d8e916f31..3edf460df 100644
--- a/src/video_core/renderer_vulkan/vk_buffer_cache.h
+++ b/src/video_core/renderer_vulkan/vk_buffer_cache.h
@@ -24,22 +24,36 @@ class VKFence;
class VKMemoryManager;
class VKStreamBuffer;
-struct CachedBufferEntry final : public RasterizerCacheObject {
- VAddr GetAddr() const override {
- return addr;
+class CachedBufferEntry final : public RasterizerCacheObject {
+public:
+ explicit CachedBufferEntry(VAddr cpu_addr, std::size_t size, u64 offset, std::size_t alignment,
+ u8* host_ptr);
+
+ VAddr GetCpuAddr() const override {
+ return cpu_addr;
}
std::size_t GetSizeInBytes() const override {
return size;
}
- // We do not have to flush this cache as things in it are never modified by us.
- void Flush() override {}
+ std::size_t GetSize() const {
+ return size;
+ }
+
+ u64 GetOffset() const {
+ return offset;
+ }
+
+ std::size_t GetAlignment() const {
+ return alignment;
+ }
- VAddr addr;
- std::size_t size;
- u64 offset;
- std::size_t alignment;
+private:
+ VAddr cpu_addr{};
+ std::size_t size{};
+ u64 offset{};
+ std::size_t alignment{};
};
class VKBufferCache final : public RasterizerCache<std::shared_ptr<CachedBufferEntry>> {
@@ -51,8 +65,7 @@ public:
/// Uploads data from a guest GPU address. Returns host's buffer offset where it's been
/// allocated.
- u64 UploadMemory(Tegra::GPUVAddr gpu_addr, std::size_t size, u64 alignment = 4,
- bool cache = true);
+ u64 UploadMemory(GPUVAddr gpu_addr, std::size_t size, u64 alignment = 4, bool cache = true);
/// Uploads from a host memory. Returns host's buffer offset where it's been allocated.
u64 UploadHostMemory(const u8* raw_pointer, std::size_t size, u64 alignment = 4);
@@ -71,6 +84,10 @@ public:
return buffer_handle;
}
+protected:
+ // We do not have to flush this cache as things in it are never modified by us.
+ void FlushObjectInner(const std::shared_ptr<CachedBufferEntry>& object) override {}
+
private:
void AlignBuffer(std::size_t alignment);
diff --git a/src/video_core/renderer_vulkan/vk_device.cpp b/src/video_core/renderer_vulkan/vk_device.cpp
index 78a4e5f0e..3b966ddc3 100644
--- a/src/video_core/renderer_vulkan/vk_device.cpp
+++ b/src/video_core/renderer_vulkan/vk_device.cpp
@@ -18,6 +18,7 @@ constexpr std::array<vk::Format, 3> Depth24UnormS8Uint = {
vk::Format::eD32SfloatS8Uint, vk::Format::eD16UnormS8Uint, {}};
constexpr std::array<vk::Format, 3> Depth16UnormS8Uint = {
vk::Format::eD24UnormS8Uint, vk::Format::eD32SfloatS8Uint, {}};
+constexpr std::array<vk::Format, 2> Astc = {vk::Format::eA8B8G8R8UnormPack32, {}};
} // namespace Alternatives
@@ -51,15 +52,19 @@ VKDevice::VKDevice(const vk::DispatchLoaderDynamic& dldi, vk::PhysicalDevice phy
: physical{physical}, format_properties{GetFormatProperties(dldi, physical)} {
SetupFamilies(dldi, surface);
SetupProperties(dldi);
+ SetupFeatures(dldi);
}
VKDevice::~VKDevice() = default;
bool VKDevice::Create(const vk::DispatchLoaderDynamic& dldi, vk::Instance instance) {
- const auto queue_cis = GetDeviceQueueCreateInfos();
- vk::PhysicalDeviceFeatures device_features{};
+ vk::PhysicalDeviceFeatures device_features;
+ device_features.vertexPipelineStoresAndAtomics = true;
+ device_features.independentBlend = true;
+ device_features.textureCompressionASTC_LDR = is_optimal_astc_supported;
- const std::vector<const char*> extensions = {VK_KHR_SWAPCHAIN_EXTENSION_NAME};
+ const auto queue_cis = GetDeviceQueueCreateInfos();
+ const std::vector<const char*> extensions = LoadExtensions(dldi);
const vk::DeviceCreateInfo device_ci({}, static_cast<u32>(queue_cis.size()), queue_cis.data(),
0, nullptr, static_cast<u32>(extensions.size()),
extensions.data(), &device_features);
@@ -90,7 +95,7 @@ vk::Format VKDevice::GetSupportedFormat(vk::Format wanted_format,
LOG_CRITICAL(Render_Vulkan,
"Format={} with usage={} and type={} has no defined alternatives and host "
"hardware does not support it",
- static_cast<u32>(wanted_format), static_cast<u32>(wanted_usage),
+ vk::to_string(wanted_format), vk::to_string(wanted_usage),
static_cast<u32>(format_type));
UNREACHABLE();
return wanted_format;
@@ -118,12 +123,35 @@ vk::Format VKDevice::GetSupportedFormat(vk::Format wanted_format,
return wanted_format;
}
+bool VKDevice::IsOptimalAstcSupported(const vk::PhysicalDeviceFeatures& features,
+ const vk::DispatchLoaderDynamic& dldi) const {
+ if (!features.textureCompressionASTC_LDR) {
+ return false;
+ }
+ const auto format_feature_usage{
+ vk::FormatFeatureFlagBits::eSampledImage | vk::FormatFeatureFlagBits::eBlitSrc |
+ vk::FormatFeatureFlagBits::eBlitDst | vk::FormatFeatureFlagBits::eTransferSrc |
+ vk::FormatFeatureFlagBits::eTransferDst};
+ constexpr std::array<vk::Format, 9> astc_formats = {
+ vk::Format::eAstc4x4UnormBlock, vk::Format::eAstc4x4SrgbBlock,
+ vk::Format::eAstc8x8SrgbBlock, vk::Format::eAstc8x6SrgbBlock,
+ vk::Format::eAstc5x4SrgbBlock, vk::Format::eAstc5x5UnormBlock,
+ vk::Format::eAstc5x5SrgbBlock, vk::Format::eAstc10x8UnormBlock,
+ vk::Format::eAstc10x8SrgbBlock};
+ for (const auto format : astc_formats) {
+ const auto format_properties{physical.getFormatProperties(format, dldi)};
+ if (!(format_properties.optimalTilingFeatures & format_feature_usage)) {
+ return false;
+ }
+ }
+ return true;
+}
+
bool VKDevice::IsFormatSupported(vk::Format wanted_format, vk::FormatFeatureFlags wanted_usage,
FormatType format_type) const {
const auto it = format_properties.find(wanted_format);
if (it == format_properties.end()) {
- LOG_CRITICAL(Render_Vulkan, "Unimplemented format query={}",
- static_cast<u32>(wanted_format));
+ LOG_CRITICAL(Render_Vulkan, "Unimplemented format query={}", vk::to_string(wanted_format));
UNREACHABLE();
return true;
}
@@ -133,11 +161,9 @@ bool VKDevice::IsFormatSupported(vk::Format wanted_format, vk::FormatFeatureFlag
bool VKDevice::IsSuitable(const vk::DispatchLoaderDynamic& dldi, vk::PhysicalDevice physical,
vk::SurfaceKHR surface) {
- const std::string swapchain_extension = VK_KHR_SWAPCHAIN_EXTENSION_NAME;
-
bool has_swapchain{};
for (const auto& prop : physical.enumerateDeviceExtensionProperties(nullptr, dldi)) {
- has_swapchain |= prop.extensionName == swapchain_extension;
+ has_swapchain |= prop.extensionName == std::string(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
}
if (!has_swapchain) {
// The device doesn't support creating swapchains.
@@ -161,8 +187,14 @@ bool VKDevice::IsSuitable(const vk::DispatchLoaderDynamic& dldi, vk::PhysicalDev
}
// TODO(Rodrigo): Check if the device matches all requeriments.
- const vk::PhysicalDeviceProperties props = physical.getProperties(dldi);
- if (props.limits.maxUniformBufferRange < 65536) {
+ const auto properties{physical.getProperties(dldi)};
+ const auto limits{properties.limits};
+ if (limits.maxUniformBufferRange < 65536) {
+ return false;
+ }
+
+ const vk::PhysicalDeviceFeatures features{physical.getFeatures(dldi)};
+ if (!features.vertexPipelineStoresAndAtomics || !features.independentBlend) {
return false;
}
@@ -170,6 +202,30 @@ bool VKDevice::IsSuitable(const vk::DispatchLoaderDynamic& dldi, vk::PhysicalDev
return true;
}
+std::vector<const char*> VKDevice::LoadExtensions(const vk::DispatchLoaderDynamic& dldi) {
+ std::vector<const char*> extensions;
+ extensions.reserve(2);
+ extensions.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
+
+ const auto Test = [&](const vk::ExtensionProperties& extension,
+ std::optional<std::reference_wrapper<bool>> status, const char* name,
+ u32 revision) {
+ if (extension.extensionName != std::string(name)) {
+ return;
+ }
+ extensions.push_back(name);
+ if (status) {
+ status->get() = true;
+ }
+ };
+
+ for (const auto& extension : physical.enumerateDeviceExtensionProperties(nullptr, dldi)) {
+ Test(extension, ext_scalar_block_layout, VK_EXT_SCALAR_BLOCK_LAYOUT_EXTENSION_NAME, 1);
+ }
+
+ return extensions;
+}
+
void VKDevice::SetupFamilies(const vk::DispatchLoaderDynamic& dldi, vk::SurfaceKHR surface) {
std::optional<u32> graphics_family_, present_family_;
@@ -197,10 +253,16 @@ void VKDevice::SetupProperties(const vk::DispatchLoaderDynamic& dldi) {
const vk::PhysicalDeviceProperties props = physical.getProperties(dldi);
device_type = props.deviceType;
uniform_buffer_alignment = static_cast<u64>(props.limits.minUniformBufferOffsetAlignment);
+ max_storage_buffer_range = static_cast<u64>(props.limits.maxStorageBufferRange);
+}
+
+void VKDevice::SetupFeatures(const vk::DispatchLoaderDynamic& dldi) {
+ const auto supported_features{physical.getFeatures(dldi)};
+ is_optimal_astc_supported = IsOptimalAstcSupported(supported_features, dldi);
}
std::vector<vk::DeviceQueueCreateInfo> VKDevice::GetDeviceQueueCreateInfos() const {
- static const float QUEUE_PRIORITY = 1.f;
+ static const float QUEUE_PRIORITY = 1.0f;
std::set<u32> unique_queue_families = {graphics_family, present_family};
std::vector<vk::DeviceQueueCreateInfo> queue_cis;
@@ -213,18 +275,43 @@ std::vector<vk::DeviceQueueCreateInfo> VKDevice::GetDeviceQueueCreateInfos() con
std::map<vk::Format, vk::FormatProperties> VKDevice::GetFormatProperties(
const vk::DispatchLoaderDynamic& dldi, vk::PhysicalDevice physical) {
+ static constexpr std::array formats{vk::Format::eA8B8G8R8UnormPack32,
+ vk::Format::eB5G6R5UnormPack16,
+ vk::Format::eA2B10G10R10UnormPack32,
+ vk::Format::eR32G32B32A32Sfloat,
+ vk::Format::eR16G16Unorm,
+ vk::Format::eR16G16Snorm,
+ vk::Format::eR8G8B8A8Srgb,
+ vk::Format::eR8Unorm,
+ vk::Format::eB10G11R11UfloatPack32,
+ vk::Format::eR32Sfloat,
+ vk::Format::eR16Sfloat,
+ vk::Format::eR16G16B16A16Sfloat,
+ vk::Format::eD32Sfloat,
+ vk::Format::eD16Unorm,
+ vk::Format::eD16UnormS8Uint,
+ vk::Format::eD24UnormS8Uint,
+ vk::Format::eD32SfloatS8Uint,
+ vk::Format::eBc1RgbaUnormBlock,
+ vk::Format::eBc2UnormBlock,
+ vk::Format::eBc3UnormBlock,
+ vk::Format::eBc4UnormBlock,
+ vk::Format::eBc5UnormBlock,
+ vk::Format::eBc5SnormBlock,
+ vk::Format::eBc7UnormBlock,
+ vk::Format::eAstc4x4UnormBlock,
+ vk::Format::eAstc4x4SrgbBlock,
+ vk::Format::eAstc8x8SrgbBlock,
+ vk::Format::eAstc8x6SrgbBlock,
+ vk::Format::eAstc5x4SrgbBlock,
+ vk::Format::eAstc5x5UnormBlock,
+ vk::Format::eAstc5x5SrgbBlock,
+ vk::Format::eAstc10x8UnormBlock,
+ vk::Format::eAstc10x8SrgbBlock};
std::map<vk::Format, vk::FormatProperties> format_properties;
-
- const auto AddFormatQuery = [&format_properties, &dldi, physical](vk::Format format) {
+ for (const auto format : formats) {
format_properties.emplace(format, physical.getFormatProperties(format, dldi));
- };
- AddFormatQuery(vk::Format::eA8B8G8R8UnormPack32);
- AddFormatQuery(vk::Format::eR5G6B5UnormPack16);
- AddFormatQuery(vk::Format::eD32Sfloat);
- AddFormatQuery(vk::Format::eD16UnormS8Uint);
- AddFormatQuery(vk::Format::eD24UnormS8Uint);
- AddFormatQuery(vk::Format::eD32SfloatS8Uint);
-
+ }
return format_properties;
}
diff --git a/src/video_core/renderer_vulkan/vk_device.h b/src/video_core/renderer_vulkan/vk_device.h
index e87c7a508..537825d8b 100644
--- a/src/video_core/renderer_vulkan/vk_device.h
+++ b/src/video_core/renderer_vulkan/vk_device.h
@@ -11,7 +11,7 @@
namespace Vulkan {
-/// Format usage descriptor
+/// Format usage descriptor.
enum class FormatType { Linear, Optimal, Buffer };
/// Handles data specific to a physical device.
@@ -34,12 +34,12 @@ public:
vk::Format GetSupportedFormat(vk::Format wanted_format, vk::FormatFeatureFlags wanted_usage,
FormatType format_type) const;
- /// Returns the dispatch loader with direct function pointers of the device
+ /// Returns the dispatch loader with direct function pointers of the device.
const vk::DispatchLoaderDynamic& GetDispatchLoader() const {
return dld;
}
- /// Returns the logical device
+ /// Returns the logical device.
vk::Device GetLogical() const {
return logical.get();
}
@@ -69,30 +69,55 @@ public:
return present_family;
}
- /// Returns if the device is integrated with the host CPU
+ /// Returns if the device is integrated with the host CPU.
bool IsIntegrated() const {
return device_type == vk::PhysicalDeviceType::eIntegratedGpu;
}
- /// Returns uniform buffer alignment requeriment
+ /// Returns uniform buffer alignment requeriment.
u64 GetUniformBufferAlignment() const {
return uniform_buffer_alignment;
}
+ /// Returns the maximum range for storage buffers.
+ u64 GetMaxStorageBufferRange() const {
+ return max_storage_buffer_range;
+ }
+
+ /// Returns true if ASTC is natively supported.
+ bool IsOptimalAstcSupported() const {
+ return is_optimal_astc_supported;
+ }
+
+ /// Returns true if the device supports VK_EXT_scalar_block_layout.
+ bool IsExtScalarBlockLayoutSupported() const {
+ return ext_scalar_block_layout;
+ }
+
/// Checks if the physical device is suitable.
static bool IsSuitable(const vk::DispatchLoaderDynamic& dldi, vk::PhysicalDevice physical,
vk::SurfaceKHR surface);
private:
+ /// Loads extensions into a vector and stores available ones in this object.
+ std::vector<const char*> LoadExtensions(const vk::DispatchLoaderDynamic& dldi);
+
/// Sets up queue families.
void SetupFamilies(const vk::DispatchLoaderDynamic& dldi, vk::SurfaceKHR surface);
/// Sets up device properties.
void SetupProperties(const vk::DispatchLoaderDynamic& dldi);
+ /// Sets up device features.
+ void SetupFeatures(const vk::DispatchLoaderDynamic& dldi);
+
/// Returns a list of queue initialization descriptors.
std::vector<vk::DeviceQueueCreateInfo> GetDeviceQueueCreateInfos() const;
+ /// Returns true if ASTC textures are natively supported.
+ bool IsOptimalAstcSupported(const vk::PhysicalDeviceFeatures& features,
+ const vk::DispatchLoaderDynamic& dldi) const;
+
/// Returns true if a format is supported.
bool IsFormatSupported(vk::Format wanted_format, vk::FormatFeatureFlags wanted_usage,
FormatType format_type) const;
@@ -101,16 +126,19 @@ private:
static std::map<vk::Format, vk::FormatProperties> GetFormatProperties(
const vk::DispatchLoaderDynamic& dldi, vk::PhysicalDevice physical);
- const vk::PhysicalDevice physical; ///< Physical device
- vk::DispatchLoaderDynamic dld; ///< Device function pointers
- UniqueDevice 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
- vk::PhysicalDeviceType device_type; ///< Physical device type
- u64 uniform_buffer_alignment{}; ///< Uniform buffer alignment requeriment
- std::map<vk::Format, vk::FormatProperties> format_properties; ///< Format properties dictionary
+ const vk::PhysicalDevice physical; ///< Physical device.
+ vk::DispatchLoaderDynamic dld; ///< Device function pointers.
+ UniqueDevice 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.
+ vk::PhysicalDeviceType device_type; ///< Physical device type.
+ u64 uniform_buffer_alignment{}; ///< Uniform buffer alignment requeriment.
+ u64 max_storage_buffer_range{}; ///< Max storage buffer size.
+ bool is_optimal_astc_supported{}; ///< Support for native ASTC.
+ bool ext_scalar_block_layout{}; ///< Support for VK_EXT_scalar_block_layout.
+ std::map<vk::Format, vk::FormatProperties> format_properties; ///< Format properties dictionary.
};
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_resource_manager.cpp b/src/video_core/renderer_vulkan/vk_resource_manager.cpp
index a1e117443..13c46e5b8 100644
--- a/src/video_core/renderer_vulkan/vk_resource_manager.cpp
+++ b/src/video_core/renderer_vulkan/vk_resource_manager.cpp
@@ -21,7 +21,7 @@ public:
CommandBufferPool(const VKDevice& device)
: VKFencedPool(COMMAND_BUFFER_POOL_SIZE), device{device} {}
- void Allocate(std::size_t begin, std::size_t end) {
+ void Allocate(std::size_t begin, std::size_t end) override {
const auto dev = device.GetLogical();
const auto& dld = device.GetDispatchLoader();
const u32 graphics_family = device.GetGraphicsFamily();
diff --git a/src/video_core/renderer_vulkan/vk_resource_manager.h b/src/video_core/renderer_vulkan/vk_resource_manager.h
index 5bfe4cead..08ee86fa6 100644
--- a/src/video_core/renderer_vulkan/vk_resource_manager.h
+++ b/src/video_core/renderer_vulkan/vk_resource_manager.h
@@ -97,7 +97,7 @@ private:
class VKFenceWatch final : public VKResource {
public:
explicit VKFenceWatch();
- ~VKFenceWatch();
+ ~VKFenceWatch() override;
/// Waits for the fence to be released.
void Wait();
diff --git a/src/video_core/renderer_vulkan/vk_sampler_cache.cpp b/src/video_core/renderer_vulkan/vk_sampler_cache.cpp
new file mode 100644
index 000000000..801826d3d
--- /dev/null
+++ b/src/video_core/renderer_vulkan/vk_sampler_cache.cpp
@@ -0,0 +1,65 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cstring>
+#include <optional>
+#include <unordered_map>
+
+#include "common/assert.h"
+#include "video_core/renderer_vulkan/declarations.h"
+#include "video_core/renderer_vulkan/maxwell_to_vk.h"
+#include "video_core/renderer_vulkan/vk_sampler_cache.h"
+#include "video_core/textures/texture.h"
+
+namespace Vulkan {
+
+static std::optional<vk::BorderColor> TryConvertBorderColor(std::array<float, 4> color) {
+ // TODO(Rodrigo): Manage integer border colors
+ if (color == std::array<float, 4>{0, 0, 0, 0}) {
+ return vk::BorderColor::eFloatTransparentBlack;
+ } else if (color == std::array<float, 4>{0, 0, 0, 1}) {
+ return vk::BorderColor::eFloatOpaqueBlack;
+ } else if (color == std::array<float, 4>{1, 1, 1, 1}) {
+ return vk::BorderColor::eFloatOpaqueWhite;
+ } else {
+ return {};
+ }
+}
+
+VKSamplerCache::VKSamplerCache(const VKDevice& device) : device{device} {}
+
+VKSamplerCache::~VKSamplerCache() = default;
+
+UniqueSampler VKSamplerCache::CreateSampler(const Tegra::Texture::TSCEntry& tsc) const {
+ const float max_anisotropy{tsc.GetMaxAnisotropy()};
+ const bool has_anisotropy{max_anisotropy > 1.0f};
+
+ const auto border_color{tsc.GetBorderColor()};
+ const auto vk_border_color{TryConvertBorderColor(border_color)};
+ UNIMPLEMENTED_IF_MSG(!vk_border_color, "Unimplemented border color {} {} {} {}",
+ border_color[0], border_color[1], border_color[2], border_color[3]);
+
+ constexpr bool unnormalized_coords{false};
+
+ const vk::SamplerCreateInfo sampler_ci(
+ {}, MaxwellToVK::Sampler::Filter(tsc.mag_filter),
+ MaxwellToVK::Sampler::Filter(tsc.min_filter),
+ MaxwellToVK::Sampler::MipmapMode(tsc.mipmap_filter),
+ MaxwellToVK::Sampler::WrapMode(tsc.wrap_u), MaxwellToVK::Sampler::WrapMode(tsc.wrap_v),
+ MaxwellToVK::Sampler::WrapMode(tsc.wrap_p), tsc.GetLodBias(), has_anisotropy,
+ max_anisotropy, tsc.depth_compare_enabled,
+ MaxwellToVK::Sampler::DepthCompareFunction(tsc.depth_compare_func), tsc.GetMinLod(),
+ tsc.GetMaxLod(), vk_border_color.value_or(vk::BorderColor::eFloatTransparentBlack),
+ unnormalized_coords);
+
+ const auto& dld{device.GetDispatchLoader()};
+ const auto dev{device.GetLogical()};
+ return dev.createSamplerUnique(sampler_ci, nullptr, dld);
+}
+
+vk::Sampler VKSamplerCache::ToSamplerType(const UniqueSampler& 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
new file mode 100644
index 000000000..771b05c73
--- /dev/null
+++ b/src/video_core/renderer_vulkan/vk_sampler_cache.h
@@ -0,0 +1,32 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <unordered_map>
+
+#include "common/common_types.h"
+#include "video_core/renderer_vulkan/declarations.h"
+#include "video_core/sampler_cache.h"
+#include "video_core/textures/texture.h"
+
+namespace Vulkan {
+
+class VKDevice;
+
+class VKSamplerCache final : public VideoCommon::SamplerCache<vk::Sampler, UniqueSampler> {
+public:
+ explicit VKSamplerCache(const VKDevice& device);
+ ~VKSamplerCache();
+
+protected:
+ UniqueSampler CreateSampler(const Tegra::Texture::TSCEntry& tsc) const;
+
+ vk::Sampler ToSamplerType(const UniqueSampler& sampler) const;
+
+private:
+ const VKDevice& device;
+};
+
+} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp
new file mode 100644
index 000000000..33ad9764a
--- /dev/null
+++ b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp
@@ -0,0 +1,1455 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <functional>
+#include <map>
+#include <set>
+
+#include <fmt/format.h>
+
+#include <sirit/sirit.h>
+
+#include "common/alignment.h"
+#include "common/assert.h"
+#include "common/common_types.h"
+#include "common/logging/log.h"
+#include "video_core/engines/maxwell_3d.h"
+#include "video_core/engines/shader_bytecode.h"
+#include "video_core/engines/shader_header.h"
+#include "video_core/renderer_vulkan/vk_device.h"
+#include "video_core/renderer_vulkan/vk_shader_decompiler.h"
+#include "video_core/shader/shader_ir.h"
+
+namespace Vulkan::VKShader {
+
+using Sirit::Id;
+using Tegra::Shader::Attribute;
+using Tegra::Shader::AttributeUse;
+using Tegra::Shader::Register;
+using namespace VideoCommon::Shader;
+
+using Maxwell = Tegra::Engines::Maxwell3D::Regs;
+using ShaderStage = Tegra::Engines::Maxwell3D::Regs::ShaderStage;
+using Operation = const OperationNode&;
+
+// TODO(Rodrigo): Use rasterizer's value
+constexpr u32 MAX_CONSTBUFFER_FLOATS = 0x4000;
+constexpr u32 MAX_CONSTBUFFER_ELEMENTS = MAX_CONSTBUFFER_FLOATS / 4;
+constexpr u32 STAGE_BINDING_STRIDE = 0x100;
+
+enum class Type { Bool, Bool2, Float, Int, Uint, HalfFloat };
+
+struct SamplerImage {
+ Id image_type;
+ Id sampled_image_type;
+ Id sampler;
+};
+
+namespace {
+
+spv::Dim GetSamplerDim(const Sampler& sampler) {
+ switch (sampler.GetType()) {
+ case Tegra::Shader::TextureType::Texture1D:
+ return spv::Dim::Dim1D;
+ case Tegra::Shader::TextureType::Texture2D:
+ return spv::Dim::Dim2D;
+ case Tegra::Shader::TextureType::Texture3D:
+ return spv::Dim::Dim3D;
+ case Tegra::Shader::TextureType::TextureCube:
+ return spv::Dim::Cube;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented sampler type={}", static_cast<u32>(sampler.GetType()));
+ return spv::Dim::Dim2D;
+ }
+}
+
+/// Returns true if an attribute index is one of the 32 generic attributes
+constexpr bool IsGenericAttribute(Attribute::Index attribute) {
+ return attribute >= Attribute::Index::Attribute_0 &&
+ attribute <= Attribute::Index::Attribute_31;
+}
+
+/// Returns the location of a generic attribute
+constexpr u32 GetGenericAttributeLocation(Attribute::Index attribute) {
+ ASSERT(IsGenericAttribute(attribute));
+ return static_cast<u32>(attribute) - static_cast<u32>(Attribute::Index::Attribute_0);
+}
+
+/// Returns true if an object has to be treated as precise
+bool IsPrecise(Operation operand) {
+ const auto& meta{operand.GetMeta()};
+ if (std::holds_alternative<MetaArithmetic>(meta)) {
+ return std::get<MetaArithmetic>(meta).precise;
+ }
+ return false;
+}
+
+} // namespace
+
+class SPIRVDecompiler : public Sirit::Module {
+public:
+ explicit SPIRVDecompiler(const VKDevice& device, const ShaderIR& ir, ShaderStage stage)
+ : Module(0x00010300), device{device}, ir{ir}, stage{stage}, header{ir.GetHeader()} {
+ AddCapability(spv::Capability::Shader);
+ AddExtension("SPV_KHR_storage_buffer_storage_class");
+ AddExtension("SPV_KHR_variable_pointers");
+ }
+
+ void Decompile() {
+ AllocateBindings();
+ AllocateLabels();
+
+ DeclareVertex();
+ DeclareGeometry();
+ DeclareFragment();
+ DeclareRegisters();
+ DeclarePredicates();
+ DeclareLocalMemory();
+ DeclareInternalFlags();
+ DeclareInputAttributes();
+ DeclareOutputAttributes();
+ DeclareConstantBuffers();
+ DeclareGlobalBuffers();
+ DeclareSamplers();
+
+ execute_function =
+ Emit(OpFunction(t_void, spv::FunctionControlMask::Inline, TypeFunction(t_void)));
+ Emit(OpLabel());
+
+ const u32 first_address = ir.GetBasicBlocks().begin()->first;
+ const Id loop_label = OpLabel("loop");
+ const Id merge_label = OpLabel("merge");
+ const Id dummy_label = OpLabel();
+ const Id jump_label = OpLabel();
+ continue_label = OpLabel("continue");
+
+ std::vector<Sirit::Literal> literals;
+ std::vector<Id> branch_labels;
+ for (const auto& pair : labels) {
+ const auto [literal, label] = pair;
+ literals.push_back(literal);
+ branch_labels.push_back(label);
+ }
+
+ jmp_to = Emit(OpVariable(TypePointer(spv::StorageClass::Function, t_uint),
+ spv::StorageClass::Function, Constant(t_uint, first_address)));
+ std::tie(ssy_flow_stack, ssy_flow_stack_top) = CreateFlowStack();
+ std::tie(pbk_flow_stack, pbk_flow_stack_top) = CreateFlowStack();
+
+ Name(jmp_to, "jmp_to");
+ Name(ssy_flow_stack, "ssy_flow_stack");
+ Name(ssy_flow_stack_top, "ssy_flow_stack_top");
+ Name(pbk_flow_stack, "pbk_flow_stack");
+ Name(pbk_flow_stack_top, "pbk_flow_stack_top");
+
+ Emit(OpBranch(loop_label));
+ Emit(loop_label);
+ Emit(OpLoopMerge(merge_label, continue_label, spv::LoopControlMask::Unroll));
+ Emit(OpBranch(dummy_label));
+
+ Emit(dummy_label);
+ const Id default_branch = OpLabel();
+ const Id jmp_to_load = Emit(OpLoad(t_uint, jmp_to));
+ Emit(OpSelectionMerge(jump_label, spv::SelectionControlMask::MaskNone));
+ Emit(OpSwitch(jmp_to_load, default_branch, literals, branch_labels));
+
+ Emit(default_branch);
+ Emit(OpReturn());
+
+ for (const auto& pair : ir.GetBasicBlocks()) {
+ const auto& [address, bb] = pair;
+ Emit(labels.at(address));
+
+ VisitBasicBlock(bb);
+
+ const auto next_it = labels.lower_bound(address + 1);
+ const Id next_label = next_it != labels.end() ? next_it->second : default_branch;
+ Emit(OpBranch(next_label));
+ }
+
+ Emit(jump_label);
+ Emit(OpBranch(continue_label));
+ Emit(continue_label);
+ Emit(OpBranch(loop_label));
+ Emit(merge_label);
+ Emit(OpReturn());
+ Emit(OpFunctionEnd());
+ }
+
+ ShaderEntries GetShaderEntries() const {
+ ShaderEntries entries;
+ entries.const_buffers_base_binding = const_buffers_base_binding;
+ entries.global_buffers_base_binding = global_buffers_base_binding;
+ entries.samplers_base_binding = samplers_base_binding;
+ for (const auto& cbuf : ir.GetConstantBuffers()) {
+ entries.const_buffers.emplace_back(cbuf.second, cbuf.first);
+ }
+ for (const auto& gmem_pair : ir.GetGlobalMemory()) {
+ const auto& [base, usage] = gmem_pair;
+ entries.global_buffers.emplace_back(base.cbuf_index, base.cbuf_offset);
+ }
+ for (const auto& sampler : ir.GetSamplers()) {
+ entries.samplers.emplace_back(sampler);
+ }
+ for (const auto& attribute : ir.GetInputAttributes()) {
+ if (IsGenericAttribute(attribute)) {
+ entries.attributes.insert(GetGenericAttributeLocation(attribute));
+ }
+ }
+ entries.clip_distances = ir.GetClipDistances();
+ entries.shader_length = ir.GetLength();
+ entries.entry_function = execute_function;
+ entries.interfaces = interfaces;
+ return entries;
+ }
+
+private:
+ using OperationDecompilerFn = Id (SPIRVDecompiler::*)(Operation);
+ using OperationDecompilersArray =
+ std::array<OperationDecompilerFn, static_cast<std::size_t>(OperationCode::Amount)>;
+
+ static constexpr auto INTERNAL_FLAGS_COUNT = static_cast<std::size_t>(InternalFlag::Amount);
+
+ void AllocateBindings() {
+ const u32 binding_base = static_cast<u32>(stage) * STAGE_BINDING_STRIDE;
+ u32 binding_iterator = binding_base;
+
+ const auto Allocate = [&binding_iterator](std::size_t count) {
+ const u32 current_binding = binding_iterator;
+ binding_iterator += static_cast<u32>(count);
+ return current_binding;
+ };
+ const_buffers_base_binding = Allocate(ir.GetConstantBuffers().size());
+ global_buffers_base_binding = Allocate(ir.GetGlobalMemory().size());
+ samplers_base_binding = Allocate(ir.GetSamplers().size());
+
+ ASSERT_MSG(binding_iterator - binding_base < STAGE_BINDING_STRIDE,
+ "Stage binding stride is too small");
+ }
+
+ void AllocateLabels() {
+ for (const auto& pair : ir.GetBasicBlocks()) {
+ const u32 address = pair.first;
+ labels.emplace(address, OpLabel(fmt::format("label_0x{:x}", address)));
+ }
+ }
+
+ void DeclareVertex() {
+ if (stage != ShaderStage::Vertex)
+ return;
+
+ DeclareVertexRedeclarations();
+ }
+
+ void DeclareGeometry() {
+ if (stage != ShaderStage::Geometry)
+ return;
+
+ UNIMPLEMENTED();
+ }
+
+ void DeclareFragment() {
+ if (stage != ShaderStage::Fragment)
+ return;
+
+ for (u32 rt = 0; rt < static_cast<u32>(frag_colors.size()); ++rt) {
+ if (!IsRenderTargetUsed(rt)) {
+ continue;
+ }
+
+ const Id id = AddGlobalVariable(OpVariable(t_out_float4, spv::StorageClass::Output));
+ Name(id, fmt::format("frag_color{}", rt));
+ Decorate(id, spv::Decoration::Location, rt);
+
+ frag_colors[rt] = id;
+ interfaces.push_back(id);
+ }
+
+ if (header.ps.omap.depth) {
+ frag_depth = AddGlobalVariable(OpVariable(t_out_float, spv::StorageClass::Output));
+ Name(frag_depth, "frag_depth");
+ Decorate(frag_depth, spv::Decoration::BuiltIn,
+ static_cast<u32>(spv::BuiltIn::FragDepth));
+
+ interfaces.push_back(frag_depth);
+ }
+
+ frag_coord = DeclareBuiltIn(spv::BuiltIn::FragCoord, spv::StorageClass::Input, t_in_float4,
+ "frag_coord");
+ front_facing = DeclareBuiltIn(spv::BuiltIn::FrontFacing, spv::StorageClass::Input,
+ t_in_bool, "front_facing");
+ }
+
+ void DeclareRegisters() {
+ for (const u32 gpr : ir.GetRegisters()) {
+ const Id id = OpVariable(t_prv_float, spv::StorageClass::Private, v_float_zero);
+ Name(id, fmt::format("gpr_{}", gpr));
+ registers.emplace(gpr, AddGlobalVariable(id));
+ }
+ }
+
+ void DeclarePredicates() {
+ for (const auto pred : ir.GetPredicates()) {
+ const Id id = OpVariable(t_prv_bool, spv::StorageClass::Private, v_false);
+ Name(id, fmt::format("pred_{}", static_cast<u32>(pred)));
+ predicates.emplace(pred, AddGlobalVariable(id));
+ }
+ }
+
+ void DeclareLocalMemory() {
+ if (const u64 local_memory_size = header.GetLocalMemorySize(); local_memory_size > 0) {
+ const auto element_count = static_cast<u32>(Common::AlignUp(local_memory_size, 4) / 4);
+ const Id type_array = TypeArray(t_float, Constant(t_uint, element_count));
+ const Id type_pointer = TypePointer(spv::StorageClass::Private, type_array);
+ Name(type_pointer, "LocalMemory");
+
+ local_memory =
+ OpVariable(type_pointer, spv::StorageClass::Private, ConstantNull(type_array));
+ AddGlobalVariable(Name(local_memory, "local_memory"));
+ }
+ }
+
+ void DeclareInternalFlags() {
+ constexpr std::array<const char*, INTERNAL_FLAGS_COUNT> names = {"zero", "sign", "carry",
+ "overflow"};
+ for (std::size_t flag = 0; flag < INTERNAL_FLAGS_COUNT; ++flag) {
+ const auto flag_code = static_cast<InternalFlag>(flag);
+ const Id id = OpVariable(t_prv_bool, spv::StorageClass::Private, v_false);
+ internal_flags[flag] = AddGlobalVariable(Name(id, names[flag]));
+ }
+ }
+
+ void DeclareInputAttributes() {
+ for (const auto index : ir.GetInputAttributes()) {
+ if (!IsGenericAttribute(index)) {
+ continue;
+ }
+
+ UNIMPLEMENTED_IF(stage == ShaderStage::Geometry);
+
+ const u32 location = GetGenericAttributeLocation(index);
+ const Id id = OpVariable(t_in_float4, spv::StorageClass::Input);
+ Name(AddGlobalVariable(id), fmt::format("in_attr{}", location));
+ input_attributes.emplace(index, id);
+ interfaces.push_back(id);
+
+ Decorate(id, spv::Decoration::Location, location);
+
+ if (stage != ShaderStage::Fragment) {
+ continue;
+ }
+ switch (header.ps.GetAttributeUse(location)) {
+ case AttributeUse::Constant:
+ Decorate(id, spv::Decoration::Flat);
+ break;
+ case AttributeUse::ScreenLinear:
+ Decorate(id, spv::Decoration::NoPerspective);
+ break;
+ case AttributeUse::Perspective:
+ // Default
+ break;
+ default:
+ UNREACHABLE_MSG("Unused attribute being fetched");
+ }
+ }
+ }
+
+ void DeclareOutputAttributes() {
+ for (const auto index : ir.GetOutputAttributes()) {
+ if (!IsGenericAttribute(index)) {
+ continue;
+ }
+ const auto location = GetGenericAttributeLocation(index);
+ const Id id = OpVariable(t_out_float4, spv::StorageClass::Output);
+ Name(AddGlobalVariable(id), fmt::format("out_attr{}", location));
+ output_attributes.emplace(index, id);
+ interfaces.push_back(id);
+
+ Decorate(id, spv::Decoration::Location, location);
+ }
+ }
+
+ void DeclareConstantBuffers() {
+ u32 binding = const_buffers_base_binding;
+ for (const auto& entry : ir.GetConstantBuffers()) {
+ const auto [index, size] = entry;
+ const Id type =
+ device.IsExtScalarBlockLayoutSupported() ? t_cbuf_scalar_ubo : t_cbuf_std140_ubo;
+ const Id id = OpVariable(type, spv::StorageClass::Uniform);
+ AddGlobalVariable(Name(id, fmt::format("cbuf_{}", index)));
+
+ Decorate(id, spv::Decoration::Binding, binding++);
+ Decorate(id, spv::Decoration::DescriptorSet, DESCRIPTOR_SET);
+ constant_buffers.emplace(index, id);
+ }
+ }
+
+ void DeclareGlobalBuffers() {
+ u32 binding = global_buffers_base_binding;
+ for (const auto& entry : ir.GetGlobalMemory()) {
+ const auto [base, usage] = entry;
+ const Id id = OpVariable(t_gmem_ssbo, spv::StorageClass::StorageBuffer);
+ AddGlobalVariable(
+ Name(id, fmt::format("gmem_{}_{}", base.cbuf_index, base.cbuf_offset)));
+
+ Decorate(id, spv::Decoration::Binding, binding++);
+ Decorate(id, spv::Decoration::DescriptorSet, DESCRIPTOR_SET);
+ global_buffers.emplace(base, id);
+ }
+ }
+
+ void DeclareSamplers() {
+ u32 binding = samplers_base_binding;
+ for (const auto& sampler : ir.GetSamplers()) {
+ const auto dim = GetSamplerDim(sampler);
+ const int depth = sampler.IsShadow() ? 1 : 0;
+ const int arrayed = sampler.IsArray() ? 1 : 0;
+ // TODO(Rodrigo): Sampled 1 indicates that the image will be used with a sampler. When
+ // SULD and SUST instructions are implemented, replace this value.
+ const int sampled = 1;
+ const Id image_type =
+ TypeImage(t_float, dim, depth, arrayed, false, sampled, spv::ImageFormat::Unknown);
+ const Id sampled_image_type = TypeSampledImage(image_type);
+ const Id pointer_type =
+ TypePointer(spv::StorageClass::UniformConstant, sampled_image_type);
+ const Id id = OpVariable(pointer_type, spv::StorageClass::UniformConstant);
+ AddGlobalVariable(Name(id, fmt::format("sampler_{}", sampler.GetIndex())));
+
+ sampler_images.insert(
+ {static_cast<u32>(sampler.GetIndex()), {image_type, sampled_image_type, id}});
+
+ Decorate(id, spv::Decoration::Binding, binding++);
+ Decorate(id, spv::Decoration::DescriptorSet, DESCRIPTOR_SET);
+ }
+ }
+
+ void DeclareVertexRedeclarations() {
+ vertex_index = DeclareBuiltIn(spv::BuiltIn::VertexIndex, spv::StorageClass::Input,
+ t_in_uint, "vertex_index");
+ instance_index = DeclareBuiltIn(spv::BuiltIn::InstanceIndex, spv::StorageClass::Input,
+ t_in_uint, "instance_index");
+
+ bool is_point_size_declared = false;
+ bool is_clip_distances_declared = false;
+ for (const auto index : ir.GetOutputAttributes()) {
+ if (index == Attribute::Index::PointSize) {
+ is_point_size_declared = true;
+ } else if (index == Attribute::Index::ClipDistances0123 ||
+ index == Attribute::Index::ClipDistances4567) {
+ is_clip_distances_declared = true;
+ }
+ }
+
+ std::vector<Id> members;
+ members.push_back(t_float4);
+ if (is_point_size_declared) {
+ members.push_back(t_float);
+ }
+ if (is_clip_distances_declared) {
+ members.push_back(TypeArray(t_float, Constant(t_uint, 8)));
+ }
+
+ const Id gl_per_vertex_struct = Name(TypeStruct(members), "PerVertex");
+ Decorate(gl_per_vertex_struct, spv::Decoration::Block);
+
+ u32 declaration_index = 0;
+ const auto MemberDecorateBuiltIn = [&](spv::BuiltIn builtin, std::string name,
+ bool condition) {
+ if (!condition)
+ return u32{};
+ MemberName(gl_per_vertex_struct, declaration_index, name);
+ MemberDecorate(gl_per_vertex_struct, declaration_index, spv::Decoration::BuiltIn,
+ static_cast<u32>(builtin));
+ return declaration_index++;
+ };
+
+ position_index = MemberDecorateBuiltIn(spv::BuiltIn::Position, "position", true);
+ point_size_index =
+ MemberDecorateBuiltIn(spv::BuiltIn::PointSize, "point_size", is_point_size_declared);
+ clip_distances_index = MemberDecorateBuiltIn(spv::BuiltIn::ClipDistance, "clip_distances",
+ is_clip_distances_declared);
+
+ const Id type_pointer = TypePointer(spv::StorageClass::Output, gl_per_vertex_struct);
+ per_vertex = OpVariable(type_pointer, spv::StorageClass::Output);
+ AddGlobalVariable(Name(per_vertex, "per_vertex"));
+ interfaces.push_back(per_vertex);
+ }
+
+ void VisitBasicBlock(const NodeBlock& bb) {
+ for (const auto& node : bb) {
+ static_cast<void>(Visit(node));
+ }
+ }
+
+ Id Visit(const Node& node) {
+ if (const auto operation = std::get_if<OperationNode>(&*node)) {
+ const auto operation_index = static_cast<std::size_t>(operation->GetCode());
+ const auto decompiler = operation_decompilers[operation_index];
+ if (decompiler == nullptr) {
+ UNREACHABLE_MSG("Operation decompiler {} not defined", operation_index);
+ }
+ return (this->*decompiler)(*operation);
+
+ } else if (const auto gpr = std::get_if<GprNode>(&*node)) {
+ const u32 index = gpr->GetIndex();
+ if (index == Register::ZeroIndex) {
+ return Constant(t_float, 0.0f);
+ }
+ return Emit(OpLoad(t_float, registers.at(index)));
+
+ } else if (const auto immediate = std::get_if<ImmediateNode>(&*node)) {
+ return BitcastTo<Type::Float>(Constant(t_uint, immediate->GetValue()));
+
+ } else if (const auto predicate = std::get_if<PredicateNode>(&*node)) {
+ const auto value = [&]() -> Id {
+ switch (const auto index = predicate->GetIndex(); index) {
+ case Tegra::Shader::Pred::UnusedIndex:
+ return v_true;
+ case Tegra::Shader::Pred::NeverExecute:
+ return v_false;
+ default:
+ return Emit(OpLoad(t_bool, predicates.at(index)));
+ }
+ }();
+ if (predicate->IsNegated()) {
+ return Emit(OpLogicalNot(t_bool, value));
+ }
+ return value;
+
+ } else if (const auto abuf = std::get_if<AbufNode>(&*node)) {
+ const auto attribute = abuf->GetIndex();
+ const auto element = abuf->GetElement();
+
+ switch (attribute) {
+ case Attribute::Index::Position:
+ if (stage != ShaderStage::Fragment) {
+ UNIMPLEMENTED();
+ break;
+ } else {
+ if (element == 3) {
+ return Constant(t_float, 1.0f);
+ }
+ return Emit(OpLoad(t_float, AccessElement(t_in_float, frag_coord, element)));
+ }
+ case Attribute::Index::TessCoordInstanceIDVertexID:
+ // TODO(Subv): Find out what the values are for the first two elements when inside a
+ // vertex shader, and what's the value of the fourth element when inside a Tess Eval
+ // shader.
+ ASSERT(stage == ShaderStage::Vertex);
+ switch (element) {
+ case 2:
+ return BitcastFrom<Type::Uint>(Emit(OpLoad(t_uint, instance_index)));
+ case 3:
+ return BitcastFrom<Type::Uint>(Emit(OpLoad(t_uint, vertex_index)));
+ }
+ UNIMPLEMENTED_MSG("Unmanaged TessCoordInstanceIDVertexID element={}", element);
+ return Constant(t_float, 0);
+ case Attribute::Index::FrontFacing:
+ // TODO(Subv): Find out what the values are for the other elements.
+ ASSERT(stage == ShaderStage::Fragment);
+ if (element == 3) {
+ const Id is_front_facing = Emit(OpLoad(t_bool, front_facing));
+ const Id true_value =
+ BitcastTo<Type::Float>(Constant(t_int, static_cast<s32>(-1)));
+ const Id false_value = BitcastTo<Type::Float>(Constant(t_int, 0));
+ return Emit(OpSelect(t_float, is_front_facing, true_value, false_value));
+ }
+ UNIMPLEMENTED_MSG("Unmanaged FrontFacing element={}", element);
+ return Constant(t_float, 0.0f);
+ default:
+ if (IsGenericAttribute(attribute)) {
+ const Id pointer =
+ AccessElement(t_in_float, input_attributes.at(attribute), element);
+ return Emit(OpLoad(t_float, pointer));
+ }
+ break;
+ }
+ UNIMPLEMENTED_MSG("Unhandled input attribute: {}", static_cast<u32>(attribute));
+
+ } else if (const auto cbuf = std::get_if<CbufNode>(&*node)) {
+ const Node& offset = cbuf->GetOffset();
+ const Id buffer_id = constant_buffers.at(cbuf->GetIndex());
+
+ Id pointer{};
+ if (device.IsExtScalarBlockLayoutSupported()) {
+ const Id buffer_offset = Emit(OpShiftRightLogical(
+ t_uint, BitcastTo<Type::Uint>(Visit(offset)), Constant(t_uint, 2u)));
+ pointer = Emit(
+ OpAccessChain(t_cbuf_float, buffer_id, Constant(t_uint, 0u), buffer_offset));
+ } else {
+ Id buffer_index{};
+ Id buffer_element{};
+ if (const auto immediate = std::get_if<ImmediateNode>(&*offset)) {
+ // Direct access
+ const u32 offset_imm = immediate->GetValue();
+ ASSERT(offset_imm % 4 == 0);
+ buffer_index = Constant(t_uint, offset_imm / 16);
+ buffer_element = Constant(t_uint, (offset_imm / 4) % 4);
+ } else if (std::holds_alternative<OperationNode>(*offset)) {
+ // Indirect access
+ const Id offset_id = BitcastTo<Type::Uint>(Visit(offset));
+ const Id unsafe_offset = Emit(OpUDiv(t_uint, offset_id, Constant(t_uint, 4)));
+ const Id final_offset = Emit(OpUMod(
+ t_uint, unsafe_offset, Constant(t_uint, MAX_CONSTBUFFER_ELEMENTS - 1)));
+ buffer_index = Emit(OpUDiv(t_uint, final_offset, Constant(t_uint, 4)));
+ buffer_element = Emit(OpUMod(t_uint, final_offset, Constant(t_uint, 4)));
+ } else {
+ UNREACHABLE_MSG("Unmanaged offset node type");
+ }
+ pointer = Emit(OpAccessChain(t_cbuf_float, buffer_id, Constant(t_uint, 0),
+ buffer_index, buffer_element));
+ }
+ return Emit(OpLoad(t_float, pointer));
+
+ } else if (const auto gmem = std::get_if<GmemNode>(&*node)) {
+ const Id gmem_buffer = global_buffers.at(gmem->GetDescriptor());
+ const Id real = BitcastTo<Type::Uint>(Visit(gmem->GetRealAddress()));
+ const Id base = BitcastTo<Type::Uint>(Visit(gmem->GetBaseAddress()));
+
+ Id offset = Emit(OpISub(t_uint, real, base));
+ offset = Emit(OpUDiv(t_uint, offset, Constant(t_uint, 4u)));
+ return Emit(OpLoad(t_float, Emit(OpAccessChain(t_gmem_float, gmem_buffer,
+ Constant(t_uint, 0u), offset))));
+
+ } else if (const auto conditional = std::get_if<ConditionalNode>(&*node)) {
+ // It's invalid to call conditional on nested nodes, use an operation instead
+ const Id true_label = OpLabel();
+ const Id skip_label = OpLabel();
+ const Id condition = Visit(conditional->GetCondition());
+ Emit(OpSelectionMerge(skip_label, spv::SelectionControlMask::MaskNone));
+ Emit(OpBranchConditional(condition, true_label, skip_label));
+ Emit(true_label);
+
+ VisitBasicBlock(conditional->GetCode());
+
+ Emit(OpBranch(skip_label));
+ Emit(skip_label);
+ return {};
+
+ } else if (const auto comment = std::get_if<CommentNode>(&*node)) {
+ Name(Emit(OpUndef(t_void)), comment->GetText());
+ return {};
+ }
+
+ UNREACHABLE();
+ return {};
+ }
+
+ template <Id (Module::*func)(Id, Id), Type result_type, Type type_a = result_type>
+ Id Unary(Operation operation) {
+ const Id type_def = GetTypeDefinition(result_type);
+ const Id op_a = VisitOperand<type_a>(operation, 0);
+
+ const Id value = BitcastFrom<result_type>(Emit((this->*func)(type_def, op_a)));
+ if (IsPrecise(operation)) {
+ Decorate(value, spv::Decoration::NoContraction);
+ }
+ return value;
+ }
+
+ template <Id (Module::*func)(Id, Id, Id), Type result_type, Type type_a = result_type,
+ Type type_b = type_a>
+ Id Binary(Operation operation) {
+ const Id type_def = GetTypeDefinition(result_type);
+ const Id op_a = VisitOperand<type_a>(operation, 0);
+ const Id op_b = VisitOperand<type_b>(operation, 1);
+
+ const Id value = BitcastFrom<result_type>(Emit((this->*func)(type_def, op_a, op_b)));
+ if (IsPrecise(operation)) {
+ Decorate(value, spv::Decoration::NoContraction);
+ }
+ return value;
+ }
+
+ template <Id (Module::*func)(Id, Id, Id, Id), Type result_type, Type type_a = result_type,
+ Type type_b = type_a, Type type_c = type_b>
+ Id Ternary(Operation operation) {
+ const Id type_def = GetTypeDefinition(result_type);
+ const Id op_a = VisitOperand<type_a>(operation, 0);
+ const Id op_b = VisitOperand<type_b>(operation, 1);
+ const Id op_c = VisitOperand<type_c>(operation, 2);
+
+ const Id value = BitcastFrom<result_type>(Emit((this->*func)(type_def, op_a, op_b, op_c)));
+ if (IsPrecise(operation)) {
+ Decorate(value, spv::Decoration::NoContraction);
+ }
+ return value;
+ }
+
+ template <Id (Module::*func)(Id, Id, Id, Id, Id), Type result_type, Type type_a = result_type,
+ Type type_b = type_a, Type type_c = type_b, Type type_d = type_c>
+ Id Quaternary(Operation operation) {
+ const Id type_def = GetTypeDefinition(result_type);
+ const Id op_a = VisitOperand<type_a>(operation, 0);
+ const Id op_b = VisitOperand<type_b>(operation, 1);
+ const Id op_c = VisitOperand<type_c>(operation, 2);
+ const Id op_d = VisitOperand<type_d>(operation, 3);
+
+ const Id value =
+ BitcastFrom<result_type>(Emit((this->*func)(type_def, op_a, op_b, op_c, op_d)));
+ if (IsPrecise(operation)) {
+ Decorate(value, spv::Decoration::NoContraction);
+ }
+ return value;
+ }
+
+ Id Assign(Operation operation) {
+ const Node& dest = operation[0];
+ const Node& src = operation[1];
+
+ Id target{};
+ if (const auto gpr = std::get_if<GprNode>(&*dest)) {
+ if (gpr->GetIndex() == Register::ZeroIndex) {
+ // Writing to Register::ZeroIndex is a no op
+ return {};
+ }
+ target = registers.at(gpr->GetIndex());
+
+ } else if (const auto abuf = std::get_if<AbufNode>(&*dest)) {
+ target = [&]() -> Id {
+ switch (const auto attribute = abuf->GetIndex(); attribute) {
+ case Attribute::Index::Position:
+ return AccessElement(t_out_float, per_vertex, position_index,
+ abuf->GetElement());
+ case Attribute::Index::PointSize:
+ return AccessElement(t_out_float, per_vertex, point_size_index);
+ case Attribute::Index::ClipDistances0123:
+ return AccessElement(t_out_float, per_vertex, clip_distances_index,
+ abuf->GetElement());
+ case Attribute::Index::ClipDistances4567:
+ return AccessElement(t_out_float, per_vertex, clip_distances_index,
+ abuf->GetElement() + 4);
+ default:
+ if (IsGenericAttribute(attribute)) {
+ return AccessElement(t_out_float, output_attributes.at(attribute),
+ abuf->GetElement());
+ }
+ UNIMPLEMENTED_MSG("Unhandled output attribute: {}",
+ static_cast<u32>(attribute));
+ return {};
+ }
+ }();
+
+ } else if (const auto lmem = std::get_if<LmemNode>(&*dest)) {
+ Id address = BitcastTo<Type::Uint>(Visit(lmem->GetAddress()));
+ address = Emit(OpUDiv(t_uint, address, Constant(t_uint, 4)));
+ target = Emit(OpAccessChain(t_prv_float, local_memory, {address}));
+ }
+
+ Emit(OpStore(target, Visit(src)));
+ return {};
+ }
+
+ Id HNegate(Operation operation) {
+ UNIMPLEMENTED();
+ return {};
+ }
+
+ Id HClamp(Operation operation) {
+ UNIMPLEMENTED();
+ return {};
+ }
+
+ Id HUnpack(Operation operation) {
+ UNIMPLEMENTED();
+ return {};
+ }
+
+ Id HMergeF32(Operation operation) {
+ UNIMPLEMENTED();
+ return {};
+ }
+
+ Id HMergeH0(Operation operation) {
+ UNIMPLEMENTED();
+ return {};
+ }
+
+ Id HMergeH1(Operation operation) {
+ UNIMPLEMENTED();
+ return {};
+ }
+
+ Id HPack2(Operation operation) {
+ UNIMPLEMENTED();
+ return {};
+ }
+
+ Id LogicalAssign(Operation operation) {
+ const Node& dest = operation[0];
+ const Node& src = operation[1];
+
+ Id target{};
+ if (const auto pred = std::get_if<PredicateNode>(&*dest)) {
+ ASSERT_MSG(!pred->IsNegated(), "Negating logical assignment");
+
+ const auto index = pred->GetIndex();
+ switch (index) {
+ case Tegra::Shader::Pred::NeverExecute:
+ case Tegra::Shader::Pred::UnusedIndex:
+ // Writing to these predicates is a no-op
+ return {};
+ }
+ target = predicates.at(index);
+
+ } else if (const auto flag = std::get_if<InternalFlagNode>(&*dest)) {
+ target = internal_flags.at(static_cast<u32>(flag->GetFlag()));
+ }
+
+ Emit(OpStore(target, Visit(src)));
+ return {};
+ }
+
+ Id LogicalPick2(Operation operation) {
+ UNIMPLEMENTED();
+ return {};
+ }
+
+ Id LogicalAll2(Operation operation) {
+ UNIMPLEMENTED();
+ return {};
+ }
+
+ Id LogicalAny2(Operation operation) {
+ UNIMPLEMENTED();
+ return {};
+ }
+
+ Id GetTextureSampler(Operation operation) {
+ const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
+ const auto entry = sampler_images.at(static_cast<u32>(meta->sampler.GetIndex()));
+ return Emit(OpLoad(entry.sampled_image_type, entry.sampler));
+ }
+
+ Id GetTextureImage(Operation operation) {
+ const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
+ const auto entry = sampler_images.at(static_cast<u32>(meta->sampler.GetIndex()));
+ return Emit(OpImage(entry.image_type, GetTextureSampler(operation)));
+ }
+
+ Id GetTextureCoordinates(Operation operation) {
+ const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
+ std::vector<Id> coords;
+ for (std::size_t i = 0; i < operation.GetOperandsCount(); ++i) {
+ coords.push_back(Visit(operation[i]));
+ }
+ if (meta->sampler.IsArray()) {
+ const Id array_integer = BitcastTo<Type::Int>(Visit(meta->array));
+ coords.push_back(Emit(OpConvertSToF(t_float, array_integer)));
+ }
+ if (meta->sampler.IsShadow()) {
+ coords.push_back(Visit(meta->depth_compare));
+ }
+
+ const std::array<Id, 4> t_float_lut = {nullptr, t_float2, t_float3, t_float4};
+ return coords.size() == 1
+ ? coords[0]
+ : Emit(OpCompositeConstruct(t_float_lut.at(coords.size() - 1), coords));
+ }
+
+ Id GetTextureElement(Operation operation, Id sample_value) {
+ const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
+ ASSERT(meta);
+ return Emit(OpCompositeExtract(t_float, sample_value, meta->element));
+ }
+
+ Id Texture(Operation operation) {
+ const Id texture = Emit(OpImageSampleImplicitLod(t_float4, GetTextureSampler(operation),
+ GetTextureCoordinates(operation)));
+ return GetTextureElement(operation, texture);
+ }
+
+ Id TextureLod(Operation operation) {
+ const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
+ const Id texture = Emit(OpImageSampleExplicitLod(
+ t_float4, GetTextureSampler(operation), GetTextureCoordinates(operation),
+ spv::ImageOperandsMask::Lod, Visit(meta->lod)));
+ return GetTextureElement(operation, texture);
+ }
+
+ Id TextureGather(Operation operation) {
+ const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
+ const auto coords = GetTextureCoordinates(operation);
+
+ Id texture;
+ if (meta->sampler.IsShadow()) {
+ texture = Emit(OpImageDrefGather(t_float4, GetTextureSampler(operation), coords,
+ Visit(meta->component)));
+ } else {
+ u32 component_value = 0;
+ if (meta->component) {
+ const auto component = std::get_if<ImmediateNode>(&*meta->component);
+ ASSERT_MSG(component, "Component is not an immediate value");
+ component_value = component->GetValue();
+ }
+ texture = Emit(OpImageGather(t_float4, GetTextureSampler(operation), coords,
+ Constant(t_uint, component_value)));
+ }
+
+ return GetTextureElement(operation, texture);
+ }
+
+ Id TextureQueryDimensions(Operation operation) {
+ const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
+ const auto image_id = GetTextureImage(operation);
+ AddCapability(spv::Capability::ImageQuery);
+
+ if (meta->element == 3) {
+ return BitcastTo<Type::Float>(Emit(OpImageQueryLevels(t_int, image_id)));
+ }
+
+ const Id lod = VisitOperand<Type::Uint>(operation, 0);
+ const std::size_t coords_count = [&]() {
+ switch (const auto type = meta->sampler.GetType(); type) {
+ case Tegra::Shader::TextureType::Texture1D:
+ return 1;
+ case Tegra::Shader::TextureType::Texture2D:
+ case Tegra::Shader::TextureType::TextureCube:
+ return 2;
+ case Tegra::Shader::TextureType::Texture3D:
+ return 3;
+ default:
+ UNREACHABLE_MSG("Invalid texture type={}", static_cast<u32>(type));
+ return 2;
+ }
+ }();
+
+ if (meta->element >= coords_count) {
+ return Constant(t_float, 0.0f);
+ }
+
+ const std::array<Id, 3> types = {t_int, t_int2, t_int3};
+ const Id sizes = Emit(OpImageQuerySizeLod(types.at(coords_count - 1), image_id, lod));
+ const Id size = Emit(OpCompositeExtract(t_int, sizes, meta->element));
+ return BitcastTo<Type::Float>(size);
+ }
+
+ Id TextureQueryLod(Operation operation) {
+ UNIMPLEMENTED();
+ return {};
+ }
+
+ Id TexelFetch(Operation operation) {
+ UNIMPLEMENTED();
+ return {};
+ }
+
+ Id Branch(Operation operation) {
+ const auto target = std::get_if<ImmediateNode>(&*operation[0]);
+ UNIMPLEMENTED_IF(!target);
+
+ Emit(OpStore(jmp_to, Constant(t_uint, target->GetValue())));
+ BranchingOp([&]() { Emit(OpBranch(continue_label)); });
+ return {};
+ }
+
+ Id PushFlowStack(Operation operation) {
+ const auto target = std::get_if<ImmediateNode>(&*operation[0]);
+ ASSERT(target);
+
+ const auto [flow_stack, flow_stack_top] = GetFlowStack(operation);
+ const Id current = Emit(OpLoad(t_uint, flow_stack_top));
+ const Id next = Emit(OpIAdd(t_uint, current, Constant(t_uint, 1)));
+ const Id access = Emit(OpAccessChain(t_func_uint, flow_stack, current));
+
+ Emit(OpStore(access, Constant(t_uint, target->GetValue())));
+ Emit(OpStore(flow_stack_top, next));
+ return {};
+ }
+
+ Id PopFlowStack(Operation operation) {
+ const auto [flow_stack, flow_stack_top] = GetFlowStack(operation);
+ const Id current = Emit(OpLoad(t_uint, flow_stack_top));
+ const Id previous = Emit(OpISub(t_uint, current, Constant(t_uint, 1)));
+ const Id access = Emit(OpAccessChain(t_func_uint, flow_stack, previous));
+ const Id target = Emit(OpLoad(t_uint, access));
+
+ Emit(OpStore(flow_stack_top, previous));
+ Emit(OpStore(jmp_to, target));
+ BranchingOp([&]() { Emit(OpBranch(continue_label)); });
+ return {};
+ }
+
+ Id Exit(Operation operation) {
+ switch (stage) {
+ case ShaderStage::Vertex: {
+ // TODO(Rodrigo): We should use VK_EXT_depth_range_unrestricted instead, but it doesn't
+ // seem to be working on Nvidia's drivers and Intel (mesa and blob) doesn't support it.
+ const Id z_pointer = AccessElement(t_out_float, per_vertex, position_index, 2u);
+ Id depth = Emit(OpLoad(t_float, z_pointer));
+ depth = Emit(OpFAdd(t_float, depth, Constant(t_float, 1.0f)));
+ depth = Emit(OpFMul(t_float, depth, Constant(t_float, 0.5f)));
+ Emit(OpStore(z_pointer, depth));
+ break;
+ }
+ case ShaderStage::Fragment: {
+ const auto SafeGetRegister = [&](u32 reg) {
+ // TODO(Rodrigo): Replace with contains once C++20 releases
+ if (const auto it = registers.find(reg); it != registers.end()) {
+ return Emit(OpLoad(t_float, it->second));
+ }
+ return Constant(t_float, 0.0f);
+ };
+
+ 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;
+ for (u32 rt = 0; rt < Maxwell::NumRenderTargets; ++rt) {
+ // TODO(Subv): Figure out how dual-source blending is configured in the Switch.
+ for (u32 component = 0; component < 4; ++component) {
+ if (header.ps.IsColorComponentOutputEnabled(rt, component)) {
+ Emit(OpStore(AccessElement(t_out_float, frag_colors.at(rt), component),
+ SafeGetRegister(current_reg)));
+ ++current_reg;
+ }
+ }
+ }
+ 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.
+ Emit(OpStore(frag_depth, SafeGetRegister(current_reg + 1)));
+ }
+ break;
+ }
+ }
+
+ BranchingOp([&]() { Emit(OpReturn()); });
+ return {};
+ }
+
+ Id Discard(Operation operation) {
+ BranchingOp([&]() { Emit(OpKill()); });
+ return {};
+ }
+
+ Id EmitVertex(Operation operation) {
+ UNIMPLEMENTED();
+ return {};
+ }
+
+ Id EndPrimitive(Operation operation) {
+ UNIMPLEMENTED();
+ return {};
+ }
+
+ Id YNegate(Operation operation) {
+ UNIMPLEMENTED();
+ return {};
+ }
+
+ template <u32 element>
+ Id LocalInvocationId(Operation) {
+ UNIMPLEMENTED();
+ return {};
+ }
+
+ template <u32 element>
+ Id WorkGroupId(Operation) {
+ UNIMPLEMENTED();
+ return {};
+ }
+
+ Id DeclareBuiltIn(spv::BuiltIn builtin, spv::StorageClass storage, Id type,
+ const std::string& name) {
+ const Id id = OpVariable(type, storage);
+ Decorate(id, spv::Decoration::BuiltIn, static_cast<u32>(builtin));
+ AddGlobalVariable(Name(id, name));
+ interfaces.push_back(id);
+ return id;
+ }
+
+ bool IsRenderTargetUsed(u32 rt) const {
+ for (u32 component = 0; component < 4; ++component) {
+ if (header.ps.IsColorComponentOutputEnabled(rt, component)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ template <typename... Args>
+ Id AccessElement(Id pointer_type, Id composite, Args... elements_) {
+ std::vector<Id> members;
+ auto elements = {elements_...};
+ for (const auto element : elements) {
+ members.push_back(Constant(t_uint, element));
+ }
+
+ return Emit(OpAccessChain(pointer_type, composite, members));
+ }
+
+ template <Type type>
+ Id VisitOperand(Operation operation, std::size_t operand_index) {
+ const Id value = Visit(operation[operand_index]);
+
+ switch (type) {
+ case Type::Bool:
+ case Type::Bool2:
+ case Type::Float:
+ return value;
+ case Type::Int:
+ return Emit(OpBitcast(t_int, value));
+ case Type::Uint:
+ return Emit(OpBitcast(t_uint, value));
+ case Type::HalfFloat:
+ UNIMPLEMENTED();
+ }
+ UNREACHABLE();
+ return value;
+ }
+
+ template <Type type>
+ Id BitcastFrom(Id value) {
+ switch (type) {
+ case Type::Bool:
+ case Type::Bool2:
+ case Type::Float:
+ return value;
+ case Type::Int:
+ case Type::Uint:
+ return Emit(OpBitcast(t_float, value));
+ case Type::HalfFloat:
+ UNIMPLEMENTED();
+ }
+ UNREACHABLE();
+ return value;
+ }
+
+ template <Type type>
+ Id BitcastTo(Id value) {
+ switch (type) {
+ case Type::Bool:
+ case Type::Bool2:
+ UNREACHABLE();
+ case Type::Float:
+ return Emit(OpBitcast(t_float, value));
+ case Type::Int:
+ return Emit(OpBitcast(t_int, value));
+ case Type::Uint:
+ return Emit(OpBitcast(t_uint, value));
+ case Type::HalfFloat:
+ UNIMPLEMENTED();
+ }
+ UNREACHABLE();
+ return value;
+ }
+
+ Id GetTypeDefinition(Type type) {
+ switch (type) {
+ case Type::Bool:
+ return t_bool;
+ case Type::Bool2:
+ return t_bool2;
+ case Type::Float:
+ return t_float;
+ case Type::Int:
+ return t_int;
+ case Type::Uint:
+ return t_uint;
+ case Type::HalfFloat:
+ UNIMPLEMENTED();
+ }
+ UNREACHABLE();
+ return {};
+ }
+
+ void BranchingOp(std::function<void()> call) {
+ const Id true_label = OpLabel();
+ const Id skip_label = OpLabel();
+ Emit(OpSelectionMerge(skip_label, spv::SelectionControlMask::Flatten));
+ Emit(OpBranchConditional(v_true, true_label, skip_label, 1, 0));
+ Emit(true_label);
+ call();
+
+ Emit(skip_label);
+ }
+
+ std::tuple<Id, Id> CreateFlowStack() {
+ // TODO(Rodrigo): Figure out the actual depth of the flow stack, for now it seems unlikely
+ // that shaders will use 20 nested SSYs and PBKs.
+ constexpr u32 FLOW_STACK_SIZE = 20;
+ constexpr auto storage_class = spv::StorageClass::Function;
+
+ const Id flow_stack_type = TypeArray(t_uint, Constant(t_uint, FLOW_STACK_SIZE));
+ const Id stack = Emit(OpVariable(TypePointer(storage_class, flow_stack_type), storage_class,
+ ConstantNull(flow_stack_type)));
+ const Id top = Emit(OpVariable(t_func_uint, storage_class, Constant(t_uint, 0)));
+ return std::tie(stack, top);
+ }
+
+ std::pair<Id, Id> GetFlowStack(Operation operation) {
+ const auto stack_class = std::get<MetaStackClass>(operation.GetMeta());
+ switch (stack_class) {
+ case MetaStackClass::Ssy:
+ return {ssy_flow_stack, ssy_flow_stack_top};
+ case MetaStackClass::Pbk:
+ return {pbk_flow_stack, pbk_flow_stack_top};
+ }
+ UNREACHABLE();
+ return {};
+ }
+
+ static constexpr OperationDecompilersArray operation_decompilers = {
+ &SPIRVDecompiler::Assign,
+
+ &SPIRVDecompiler::Ternary<&Module::OpSelect, Type::Float, Type::Bool, Type::Float,
+ Type::Float>,
+
+ &SPIRVDecompiler::Binary<&Module::OpFAdd, Type::Float>,
+ &SPIRVDecompiler::Binary<&Module::OpFMul, Type::Float>,
+ &SPIRVDecompiler::Binary<&Module::OpFDiv, Type::Float>,
+ &SPIRVDecompiler::Ternary<&Module::OpFma, Type::Float>,
+ &SPIRVDecompiler::Unary<&Module::OpFNegate, Type::Float>,
+ &SPIRVDecompiler::Unary<&Module::OpFAbs, Type::Float>,
+ &SPIRVDecompiler::Ternary<&Module::OpFClamp, Type::Float>,
+ &SPIRVDecompiler::Binary<&Module::OpFMin, Type::Float>,
+ &SPIRVDecompiler::Binary<&Module::OpFMax, Type::Float>,
+ &SPIRVDecompiler::Unary<&Module::OpCos, Type::Float>,
+ &SPIRVDecompiler::Unary<&Module::OpSin, Type::Float>,
+ &SPIRVDecompiler::Unary<&Module::OpExp2, Type::Float>,
+ &SPIRVDecompiler::Unary<&Module::OpLog2, Type::Float>,
+ &SPIRVDecompiler::Unary<&Module::OpInverseSqrt, Type::Float>,
+ &SPIRVDecompiler::Unary<&Module::OpSqrt, Type::Float>,
+ &SPIRVDecompiler::Unary<&Module::OpRoundEven, Type::Float>,
+ &SPIRVDecompiler::Unary<&Module::OpFloor, Type::Float>,
+ &SPIRVDecompiler::Unary<&Module::OpCeil, Type::Float>,
+ &SPIRVDecompiler::Unary<&Module::OpTrunc, Type::Float>,
+ &SPIRVDecompiler::Unary<&Module::OpConvertSToF, Type::Float, Type::Int>,
+ &SPIRVDecompiler::Unary<&Module::OpConvertUToF, Type::Float, Type::Uint>,
+
+ &SPIRVDecompiler::Binary<&Module::OpIAdd, Type::Int>,
+ &SPIRVDecompiler::Binary<&Module::OpIMul, Type::Int>,
+ &SPIRVDecompiler::Binary<&Module::OpSDiv, Type::Int>,
+ &SPIRVDecompiler::Unary<&Module::OpSNegate, Type::Int>,
+ &SPIRVDecompiler::Unary<&Module::OpSAbs, Type::Int>,
+ &SPIRVDecompiler::Binary<&Module::OpSMin, Type::Int>,
+ &SPIRVDecompiler::Binary<&Module::OpSMax, Type::Int>,
+
+ &SPIRVDecompiler::Unary<&Module::OpConvertFToS, Type::Int, Type::Float>,
+ &SPIRVDecompiler::Unary<&Module::OpBitcast, Type::Int, Type::Uint>,
+ &SPIRVDecompiler::Binary<&Module::OpShiftLeftLogical, Type::Int, Type::Int, Type::Uint>,
+ &SPIRVDecompiler::Binary<&Module::OpShiftRightLogical, Type::Int, Type::Int, Type::Uint>,
+ &SPIRVDecompiler::Binary<&Module::OpShiftRightArithmetic, Type::Int, Type::Int, Type::Uint>,
+ &SPIRVDecompiler::Binary<&Module::OpBitwiseAnd, Type::Int>,
+ &SPIRVDecompiler::Binary<&Module::OpBitwiseOr, Type::Int>,
+ &SPIRVDecompiler::Binary<&Module::OpBitwiseXor, Type::Int>,
+ &SPIRVDecompiler::Unary<&Module::OpNot, Type::Int>,
+ &SPIRVDecompiler::Quaternary<&Module::OpBitFieldInsert, Type::Int>,
+ &SPIRVDecompiler::Ternary<&Module::OpBitFieldSExtract, Type::Int>,
+ &SPIRVDecompiler::Unary<&Module::OpBitCount, Type::Int>,
+
+ &SPIRVDecompiler::Binary<&Module::OpIAdd, Type::Uint>,
+ &SPIRVDecompiler::Binary<&Module::OpIMul, Type::Uint>,
+ &SPIRVDecompiler::Binary<&Module::OpUDiv, Type::Uint>,
+ &SPIRVDecompiler::Binary<&Module::OpUMin, Type::Uint>,
+ &SPIRVDecompiler::Binary<&Module::OpUMax, Type::Uint>,
+ &SPIRVDecompiler::Unary<&Module::OpConvertFToU, Type::Uint, Type::Float>,
+ &SPIRVDecompiler::Unary<&Module::OpBitcast, Type::Uint, Type::Int>,
+ &SPIRVDecompiler::Binary<&Module::OpShiftLeftLogical, Type::Uint>,
+ &SPIRVDecompiler::Binary<&Module::OpShiftRightLogical, Type::Uint>,
+ &SPIRVDecompiler::Binary<&Module::OpShiftRightArithmetic, Type::Uint>,
+ &SPIRVDecompiler::Binary<&Module::OpBitwiseAnd, Type::Uint>,
+ &SPIRVDecompiler::Binary<&Module::OpBitwiseOr, Type::Uint>,
+ &SPIRVDecompiler::Binary<&Module::OpBitwiseXor, Type::Uint>,
+ &SPIRVDecompiler::Unary<&Module::OpNot, Type::Uint>,
+ &SPIRVDecompiler::Quaternary<&Module::OpBitFieldInsert, Type::Uint>,
+ &SPIRVDecompiler::Ternary<&Module::OpBitFieldUExtract, Type::Uint>,
+ &SPIRVDecompiler::Unary<&Module::OpBitCount, Type::Uint>,
+
+ &SPIRVDecompiler::Binary<&Module::OpFAdd, Type::HalfFloat>,
+ &SPIRVDecompiler::Binary<&Module::OpFMul, Type::HalfFloat>,
+ &SPIRVDecompiler::Ternary<&Module::OpFma, Type::HalfFloat>,
+ &SPIRVDecompiler::Unary<&Module::OpFAbs, Type::HalfFloat>,
+ &SPIRVDecompiler::HNegate,
+ &SPIRVDecompiler::HClamp,
+ &SPIRVDecompiler::HUnpack,
+ &SPIRVDecompiler::HMergeF32,
+ &SPIRVDecompiler::HMergeH0,
+ &SPIRVDecompiler::HMergeH1,
+ &SPIRVDecompiler::HPack2,
+
+ &SPIRVDecompiler::LogicalAssign,
+ &SPIRVDecompiler::Binary<&Module::OpLogicalAnd, Type::Bool>,
+ &SPIRVDecompiler::Binary<&Module::OpLogicalOr, Type::Bool>,
+ &SPIRVDecompiler::Binary<&Module::OpLogicalNotEqual, Type::Bool>,
+ &SPIRVDecompiler::Unary<&Module::OpLogicalNot, Type::Bool>,
+ &SPIRVDecompiler::LogicalPick2,
+ &SPIRVDecompiler::LogicalAll2,
+ &SPIRVDecompiler::LogicalAny2,
+
+ &SPIRVDecompiler::Binary<&Module::OpFOrdLessThan, Type::Bool, Type::Float>,
+ &SPIRVDecompiler::Binary<&Module::OpFOrdEqual, Type::Bool, Type::Float>,
+ &SPIRVDecompiler::Binary<&Module::OpFOrdLessThanEqual, Type::Bool, Type::Float>,
+ &SPIRVDecompiler::Binary<&Module::OpFOrdGreaterThan, Type::Bool, Type::Float>,
+ &SPIRVDecompiler::Binary<&Module::OpFOrdNotEqual, Type::Bool, Type::Float>,
+ &SPIRVDecompiler::Binary<&Module::OpFOrdGreaterThanEqual, Type::Bool, Type::Float>,
+ &SPIRVDecompiler::Unary<&Module::OpIsNan, Type::Bool>,
+
+ &SPIRVDecompiler::Binary<&Module::OpSLessThan, Type::Bool, Type::Int>,
+ &SPIRVDecompiler::Binary<&Module::OpIEqual, Type::Bool, Type::Int>,
+ &SPIRVDecompiler::Binary<&Module::OpSLessThanEqual, Type::Bool, Type::Int>,
+ &SPIRVDecompiler::Binary<&Module::OpSGreaterThan, Type::Bool, Type::Int>,
+ &SPIRVDecompiler::Binary<&Module::OpINotEqual, Type::Bool, Type::Int>,
+ &SPIRVDecompiler::Binary<&Module::OpSGreaterThanEqual, Type::Bool, Type::Int>,
+
+ &SPIRVDecompiler::Binary<&Module::OpULessThan, Type::Bool, Type::Uint>,
+ &SPIRVDecompiler::Binary<&Module::OpIEqual, Type::Bool, Type::Uint>,
+ &SPIRVDecompiler::Binary<&Module::OpULessThanEqual, Type::Bool, Type::Uint>,
+ &SPIRVDecompiler::Binary<&Module::OpUGreaterThan, Type::Bool, Type::Uint>,
+ &SPIRVDecompiler::Binary<&Module::OpINotEqual, Type::Bool, Type::Uint>,
+ &SPIRVDecompiler::Binary<&Module::OpUGreaterThanEqual, Type::Bool, Type::Uint>,
+
+ &SPIRVDecompiler::Binary<&Module::OpFOrdLessThan, Type::Bool, Type::HalfFloat>,
+ &SPIRVDecompiler::Binary<&Module::OpFOrdEqual, Type::Bool, Type::HalfFloat>,
+ &SPIRVDecompiler::Binary<&Module::OpFOrdLessThanEqual, Type::Bool, Type::HalfFloat>,
+ &SPIRVDecompiler::Binary<&Module::OpFOrdGreaterThan, Type::Bool, Type::HalfFloat>,
+ &SPIRVDecompiler::Binary<&Module::OpFOrdNotEqual, Type::Bool, Type::HalfFloat>,
+ &SPIRVDecompiler::Binary<&Module::OpFOrdGreaterThanEqual, Type::Bool, Type::HalfFloat>,
+ // TODO(Rodrigo): Should these use the OpFUnord* variants?
+ &SPIRVDecompiler::Binary<&Module::OpFOrdLessThan, Type::Bool, Type::HalfFloat>,
+ &SPIRVDecompiler::Binary<&Module::OpFOrdEqual, Type::Bool, Type::HalfFloat>,
+ &SPIRVDecompiler::Binary<&Module::OpFOrdLessThanEqual, Type::Bool, Type::HalfFloat>,
+ &SPIRVDecompiler::Binary<&Module::OpFOrdGreaterThan, Type::Bool, Type::HalfFloat>,
+ &SPIRVDecompiler::Binary<&Module::OpFOrdNotEqual, Type::Bool, Type::HalfFloat>,
+ &SPIRVDecompiler::Binary<&Module::OpFOrdGreaterThanEqual, Type::Bool, Type::HalfFloat>,
+
+ &SPIRVDecompiler::Texture,
+ &SPIRVDecompiler::TextureLod,
+ &SPIRVDecompiler::TextureGather,
+ &SPIRVDecompiler::TextureQueryDimensions,
+ &SPIRVDecompiler::TextureQueryLod,
+ &SPIRVDecompiler::TexelFetch,
+
+ &SPIRVDecompiler::Branch,
+ &SPIRVDecompiler::PushFlowStack,
+ &SPIRVDecompiler::PopFlowStack,
+ &SPIRVDecompiler::Exit,
+ &SPIRVDecompiler::Discard,
+
+ &SPIRVDecompiler::EmitVertex,
+ &SPIRVDecompiler::EndPrimitive,
+
+ &SPIRVDecompiler::YNegate,
+ &SPIRVDecompiler::LocalInvocationId<0>,
+ &SPIRVDecompiler::LocalInvocationId<1>,
+ &SPIRVDecompiler::LocalInvocationId<2>,
+ &SPIRVDecompiler::WorkGroupId<0>,
+ &SPIRVDecompiler::WorkGroupId<1>,
+ &SPIRVDecompiler::WorkGroupId<2>,
+ };
+
+ const VKDevice& device;
+ const ShaderIR& ir;
+ const ShaderStage stage;
+ const Tegra::Shader::Header header;
+
+ const Id t_void = Name(TypeVoid(), "void");
+
+ const Id t_bool = Name(TypeBool(), "bool");
+ const Id t_bool2 = Name(TypeVector(t_bool, 2), "bool2");
+
+ const Id t_int = Name(TypeInt(32, true), "int");
+ const Id t_int2 = Name(TypeVector(t_int, 2), "int2");
+ const Id t_int3 = Name(TypeVector(t_int, 3), "int3");
+ const Id t_int4 = Name(TypeVector(t_int, 4), "int4");
+
+ const Id t_uint = Name(TypeInt(32, false), "uint");
+ const Id t_uint2 = Name(TypeVector(t_uint, 2), "uint2");
+ const Id t_uint3 = Name(TypeVector(t_uint, 3), "uint3");
+ const Id t_uint4 = Name(TypeVector(t_uint, 4), "uint4");
+
+ const Id t_float = Name(TypeFloat(32), "float");
+ const Id t_float2 = Name(TypeVector(t_float, 2), "float2");
+ const Id t_float3 = Name(TypeVector(t_float, 3), "float3");
+ const Id t_float4 = Name(TypeVector(t_float, 4), "float4");
+
+ const Id t_prv_bool = Name(TypePointer(spv::StorageClass::Private, t_bool), "prv_bool");
+ const Id t_prv_float = Name(TypePointer(spv::StorageClass::Private, t_float), "prv_float");
+
+ const Id t_func_uint = Name(TypePointer(spv::StorageClass::Function, t_uint), "func_uint");
+
+ const Id t_in_bool = Name(TypePointer(spv::StorageClass::Input, t_bool), "in_bool");
+ const Id t_in_uint = Name(TypePointer(spv::StorageClass::Input, t_uint), "in_uint");
+ const Id t_in_float = Name(TypePointer(spv::StorageClass::Input, t_float), "in_float");
+ const Id t_in_float4 = Name(TypePointer(spv::StorageClass::Input, t_float4), "in_float4");
+
+ const Id t_out_float = Name(TypePointer(spv::StorageClass::Output, t_float), "out_float");
+ const Id t_out_float4 = Name(TypePointer(spv::StorageClass::Output, t_float4), "out_float4");
+
+ const Id t_cbuf_float = TypePointer(spv::StorageClass::Uniform, t_float);
+ const Id t_cbuf_std140 = Decorate(
+ Name(TypeArray(t_float4, Constant(t_uint, MAX_CONSTBUFFER_ELEMENTS)), "CbufStd140Array"),
+ spv::Decoration::ArrayStride, 16u);
+ const Id t_cbuf_scalar = Decorate(
+ Name(TypeArray(t_float, Constant(t_uint, MAX_CONSTBUFFER_FLOATS)), "CbufScalarArray"),
+ spv::Decoration::ArrayStride, 4u);
+ const Id t_cbuf_std140_struct = MemberDecorate(
+ Decorate(TypeStruct(t_cbuf_std140), spv::Decoration::Block), 0, spv::Decoration::Offset, 0);
+ const Id t_cbuf_scalar_struct = MemberDecorate(
+ Decorate(TypeStruct(t_cbuf_scalar), spv::Decoration::Block), 0, spv::Decoration::Offset, 0);
+ const Id t_cbuf_std140_ubo = TypePointer(spv::StorageClass::Uniform, t_cbuf_std140_struct);
+ const Id t_cbuf_scalar_ubo = TypePointer(spv::StorageClass::Uniform, t_cbuf_scalar_struct);
+
+ const Id t_gmem_float = TypePointer(spv::StorageClass::StorageBuffer, t_float);
+ const Id t_gmem_array =
+ Name(Decorate(TypeRuntimeArray(t_float), spv::Decoration::ArrayStride, 4u), "GmemArray");
+ const Id t_gmem_struct = MemberDecorate(
+ Decorate(TypeStruct(t_gmem_array), spv::Decoration::Block), 0, spv::Decoration::Offset, 0);
+ const Id t_gmem_ssbo = TypePointer(spv::StorageClass::StorageBuffer, t_gmem_struct);
+
+ const Id v_float_zero = Constant(t_float, 0.0f);
+ const Id v_true = ConstantTrue(t_bool);
+ const Id v_false = ConstantFalse(t_bool);
+
+ Id per_vertex{};
+ std::map<u32, Id> registers;
+ std::map<Tegra::Shader::Pred, Id> predicates;
+ Id local_memory{};
+ std::array<Id, INTERNAL_FLAGS_COUNT> internal_flags{};
+ std::map<Attribute::Index, Id> input_attributes;
+ std::map<Attribute::Index, Id> output_attributes;
+ std::map<u32, Id> constant_buffers;
+ std::map<GlobalMemoryBase, Id> global_buffers;
+ std::map<u32, SamplerImage> sampler_images;
+
+ Id instance_index{};
+ Id vertex_index{};
+ std::array<Id, Maxwell::NumRenderTargets> frag_colors{};
+ Id frag_depth{};
+ Id frag_coord{};
+ Id front_facing{};
+
+ u32 position_index{};
+ u32 point_size_index{};
+ u32 clip_distances_index{};
+
+ std::vector<Id> interfaces;
+
+ u32 const_buffers_base_binding{};
+ u32 global_buffers_base_binding{};
+ u32 samplers_base_binding{};
+
+ Id execute_function{};
+ Id jmp_to{};
+ Id ssy_flow_stack_top{};
+ Id pbk_flow_stack_top{};
+ Id ssy_flow_stack{};
+ Id pbk_flow_stack{};
+ Id continue_label{};
+ std::map<u32, Id> labels;
+};
+
+DecompilerResult Decompile(const VKDevice& device, const VideoCommon::Shader::ShaderIR& ir,
+ Maxwell::ShaderStage stage) {
+ auto decompiler = std::make_unique<SPIRVDecompiler>(device, ir, stage);
+ decompiler->Decompile();
+ return {std::move(decompiler), decompiler->GetShaderEntries()};
+}
+
+} // namespace Vulkan::VKShader
diff --git a/src/video_core/renderer_vulkan/vk_shader_decompiler.h b/src/video_core/renderer_vulkan/vk_shader_decompiler.h
new file mode 100644
index 000000000..f90541cc1
--- /dev/null
+++ b/src/video_core/renderer_vulkan/vk_shader_decompiler.h
@@ -0,0 +1,84 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <memory>
+#include <set>
+#include <utility>
+#include <vector>
+
+#include <sirit/sirit.h>
+
+#include "common/common_types.h"
+#include "video_core/engines/maxwell_3d.h"
+#include "video_core/shader/shader_ir.h"
+
+namespace VideoCommon::Shader {
+class ShaderIR;
+}
+
+namespace Vulkan {
+class VKDevice;
+}
+
+namespace Vulkan::VKShader {
+
+using Maxwell = Tegra::Engines::Maxwell3D::Regs;
+using SamplerEntry = VideoCommon::Shader::Sampler;
+
+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} {}
+
+ constexpr u32 GetIndex() const {
+ return index;
+ }
+
+private:
+ u32 index{};
+};
+
+class GlobalBufferEntry {
+public:
+ explicit GlobalBufferEntry(u32 cbuf_index, u32 cbuf_offset)
+ : cbuf_index{cbuf_index}, cbuf_offset{cbuf_offset} {}
+
+ u32 GetCbufIndex() const {
+ return cbuf_index;
+ }
+
+ u32 GetCbufOffset() const {
+ return cbuf_offset;
+ }
+
+private:
+ u32 cbuf_index{};
+ u32 cbuf_offset{};
+};
+
+struct ShaderEntries {
+ u32 const_buffers_base_binding{};
+ u32 global_buffers_base_binding{};
+ u32 samplers_base_binding{};
+ std::vector<ConstBufferEntry> const_buffers;
+ std::vector<GlobalBufferEntry> global_buffers;
+ std::vector<SamplerEntry> samplers;
+ std::set<u32> attributes;
+ std::array<bool, Maxwell::NumClipDistances> clip_distances{};
+ std::size_t shader_length{};
+ Sirit::Id entry_function{};
+ std::vector<Sirit::Id> interfaces;
+};
+
+using DecompilerResult = std::pair<std::unique_ptr<Sirit::Module>, ShaderEntries>;
+
+DecompilerResult Decompile(const VKDevice& device, const VideoCommon::Shader::ShaderIR& ir,
+ Maxwell::ShaderStage stage);
+
+} // namespace Vulkan::VKShader
diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp
new file mode 100644
index 000000000..08279e562
--- /dev/null
+++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp
@@ -0,0 +1,210 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <array>
+#include <limits>
+#include <vector>
+
+#include "common/assert.h"
+#include "common/logging/log.h"
+#include "core/core.h"
+#include "core/frontend/framebuffer_layout.h"
+#include "video_core/renderer_vulkan/declarations.h"
+#include "video_core/renderer_vulkan/vk_device.h"
+#include "video_core/renderer_vulkan/vk_resource_manager.h"
+#include "video_core/renderer_vulkan/vk_swapchain.h"
+
+namespace Vulkan {
+
+namespace {
+vk::SurfaceFormatKHR ChooseSwapSurfaceFormat(const std::vector<vk::SurfaceFormatKHR>& formats) {
+ if (formats.size() == 1 && formats[0].format == vk::Format::eUndefined) {
+ return {vk::Format::eB8G8R8A8Unorm, vk::ColorSpaceKHR::eSrgbNonlinear};
+ }
+ const auto& found = std::find_if(formats.begin(), formats.end(), [](const auto& format) {
+ return format.format == vk::Format::eB8G8R8A8Unorm &&
+ format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear;
+ });
+ return found != formats.end() ? *found : formats[0];
+}
+
+vk::PresentModeKHR ChooseSwapPresentMode(const std::vector<vk::PresentModeKHR>& modes) {
+ // Mailbox doesn't lock the application like fifo (vsync), prefer it
+ const auto& found = std::find_if(modes.begin(), modes.end(), [](const auto& mode) {
+ return mode == vk::PresentModeKHR::eMailbox;
+ });
+ return found != modes.end() ? *found : vk::PresentModeKHR::eFifo;
+}
+
+vk::Extent2D ChooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities, u32 width,
+ u32 height) {
+ constexpr auto undefined_size{std::numeric_limits<u32>::max()};
+ if (capabilities.currentExtent.width != undefined_size) {
+ return capabilities.currentExtent;
+ }
+ vk::Extent2D extent = {width, height};
+ extent.width = std::max(capabilities.minImageExtent.width,
+ std::min(capabilities.maxImageExtent.width, extent.width));
+ extent.height = std::max(capabilities.minImageExtent.height,
+ std::min(capabilities.maxImageExtent.height, extent.height));
+ return extent;
+}
+} // namespace
+
+VKSwapchain::VKSwapchain(vk::SurfaceKHR surface, const VKDevice& device)
+ : surface{surface}, device{device} {}
+
+VKSwapchain::~VKSwapchain() = default;
+
+void VKSwapchain::Create(u32 width, u32 height) {
+ const auto dev = device.GetLogical();
+ const auto& dld = device.GetDispatchLoader();
+ const auto physical_device = device.GetPhysical();
+
+ const vk::SurfaceCapabilitiesKHR capabilities{
+ physical_device.getSurfaceCapabilitiesKHR(surface, dld)};
+ if (capabilities.maxImageExtent.width == 0 || capabilities.maxImageExtent.height == 0) {
+ return;
+ }
+
+ dev.waitIdle(dld);
+ Destroy();
+
+ CreateSwapchain(capabilities, width, height);
+ CreateSemaphores();
+ CreateImageViews();
+
+ fences.resize(image_count, nullptr);
+}
+
+void VKSwapchain::AcquireNextImage() {
+ const auto dev{device.GetLogical()};
+ const auto& dld{device.GetDispatchLoader()};
+ dev.acquireNextImageKHR(*swapchain, std::numeric_limits<u64>::max(),
+ *present_semaphores[frame_index], {}, &image_index, dld);
+
+ if (auto& fence = fences[image_index]; fence) {
+ fence->Wait();
+ fence->Release();
+ fence = nullptr;
+ }
+}
+
+bool VKSwapchain::Present(vk::Semaphore render_semaphore, VKFence& fence) {
+ const vk::Semaphore present_semaphore{*present_semaphores[frame_index]};
+ const std::array<vk::Semaphore, 2> semaphores{present_semaphore, render_semaphore};
+ const u32 wait_semaphore_count{render_semaphore ? 2U : 1U};
+ const auto& dld{device.GetDispatchLoader()};
+ const auto present_queue{device.GetPresentQueue()};
+ bool recreated = false;
+
+ const vk::PresentInfoKHR present_info(wait_semaphore_count, semaphores.data(), 1,
+ &swapchain.get(), &image_index, {});
+ switch (const auto result = present_queue.presentKHR(&present_info, dld); result) {
+ case vk::Result::eSuccess:
+ break;
+ case vk::Result::eErrorOutOfDateKHR:
+ if (current_width > 0 && current_height > 0) {
+ Create(current_width, current_height);
+ recreated = true;
+ }
+ break;
+ default:
+ LOG_CRITICAL(Render_Vulkan, "Vulkan failed to present swapchain due to {}!",
+ vk::to_string(result));
+ UNREACHABLE();
+ }
+
+ ASSERT(fences[image_index] == nullptr);
+ fences[image_index] = &fence;
+ frame_index = (frame_index + 1) % image_count;
+ return recreated;
+}
+
+bool VKSwapchain::HasFramebufferChanged(const Layout::FramebufferLayout& framebuffer) const {
+ // TODO(Rodrigo): Handle framebuffer pixel format changes
+ return framebuffer.width != current_width || framebuffer.height != current_height;
+}
+
+void VKSwapchain::CreateSwapchain(const vk::SurfaceCapabilitiesKHR& capabilities, u32 width,
+ u32 height) {
+ const auto dev{device.GetLogical()};
+ const auto& dld{device.GetDispatchLoader()};
+ const auto physical_device{device.GetPhysical()};
+
+ const std::vector<vk::SurfaceFormatKHR> formats{
+ physical_device.getSurfaceFormatsKHR(surface, dld)};
+
+ const std::vector<vk::PresentModeKHR> present_modes{
+ physical_device.getSurfacePresentModesKHR(surface, dld)};
+
+ const vk::SurfaceFormatKHR surface_format{ChooseSwapSurfaceFormat(formats)};
+ const vk::PresentModeKHR present_mode{ChooseSwapPresentMode(present_modes)};
+ extent = ChooseSwapExtent(capabilities, width, height);
+
+ current_width = extent.width;
+ current_height = extent.height;
+
+ u32 requested_image_count{capabilities.minImageCount + 1};
+ if (capabilities.maxImageCount > 0 && requested_image_count > capabilities.maxImageCount) {
+ requested_image_count = capabilities.maxImageCount;
+ }
+
+ vk::SwapchainCreateInfoKHR swapchain_ci(
+ {}, surface, requested_image_count, surface_format.format, surface_format.colorSpace,
+ extent, 1, vk::ImageUsageFlagBits::eColorAttachment, {}, {}, {},
+ capabilities.currentTransform, vk::CompositeAlphaFlagBitsKHR::eOpaque, present_mode, false,
+ {});
+
+ const u32 graphics_family{device.GetGraphicsFamily()};
+ const u32 present_family{device.GetPresentFamily()};
+ const std::array<u32, 2> queue_indices{graphics_family, present_family};
+ if (graphics_family != present_family) {
+ swapchain_ci.imageSharingMode = vk::SharingMode::eConcurrent;
+ swapchain_ci.queueFamilyIndexCount = static_cast<u32>(queue_indices.size());
+ swapchain_ci.pQueueFamilyIndices = queue_indices.data();
+ } else {
+ swapchain_ci.imageSharingMode = vk::SharingMode::eExclusive;
+ }
+
+ swapchain = dev.createSwapchainKHRUnique(swapchain_ci, nullptr, dld);
+
+ images = dev.getSwapchainImagesKHR(*swapchain, dld);
+ image_count = static_cast<u32>(images.size());
+ image_format = surface_format.format;
+}
+
+void VKSwapchain::CreateSemaphores() {
+ const auto dev{device.GetLogical()};
+ const auto& dld{device.GetDispatchLoader()};
+
+ present_semaphores.resize(image_count);
+ for (std::size_t i = 0; i < image_count; i++) {
+ present_semaphores[i] = dev.createSemaphoreUnique({}, nullptr, dld);
+ }
+}
+
+void VKSwapchain::CreateImageViews() {
+ const auto dev{device.GetLogical()};
+ const auto& dld{device.GetDispatchLoader()};
+
+ image_views.resize(image_count);
+ for (std::size_t i = 0; i < image_count; i++) {
+ const vk::ImageViewCreateInfo image_view_ci({}, images[i], vk::ImageViewType::e2D,
+ image_format, {},
+ {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1});
+ image_views[i] = dev.createImageViewUnique(image_view_ci, nullptr, dld);
+ }
+}
+
+void VKSwapchain::Destroy() {
+ frame_index = 0;
+ present_semaphores.clear();
+ framebuffers.clear();
+ image_views.clear();
+ swapchain.reset();
+}
+
+} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_swapchain.h b/src/video_core/renderer_vulkan/vk_swapchain.h
new file mode 100644
index 000000000..2ad84f185
--- /dev/null
+++ b/src/video_core/renderer_vulkan/vk_swapchain.h
@@ -0,0 +1,92 @@
+// Copyright 2019 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"
+#include "video_core/renderer_vulkan/declarations.h"
+
+namespace Layout {
+struct FramebufferLayout;
+}
+
+namespace Vulkan {
+
+class VKDevice;
+class VKFence;
+
+class VKSwapchain {
+public:
+ explicit VKSwapchain(vk::SurfaceKHR surface, const VKDevice& device);
+ ~VKSwapchain();
+
+ /// Creates (or recreates) the swapchain with a given size.
+ void Create(u32 width, u32 height);
+
+ /// Acquires the next image in the swapchain, waits as needed.
+ void AcquireNextImage();
+
+ /// 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(vk::Semaphore render_semaphore, VKFence& fence);
+
+ /// Returns true when the framebuffer layout has changed.
+ bool HasFramebufferChanged(const Layout::FramebufferLayout& framebuffer) const;
+
+ const vk::Extent2D& GetSize() const {
+ return extent;
+ }
+
+ u32 GetImageCount() const {
+ return image_count;
+ }
+
+ u32 GetImageIndex() const {
+ return image_index;
+ }
+
+ vk::Image GetImageIndex(u32 index) const {
+ return images[index];
+ }
+
+ vk::ImageView GetImageViewIndex(u32 index) const {
+ return *image_views[index];
+ }
+
+ vk::Format GetImageFormat() const {
+ return image_format;
+ }
+
+private:
+ void CreateSwapchain(const vk::SurfaceCapabilitiesKHR& capabilities, u32 width, u32 height);
+ void CreateSemaphores();
+ void CreateImageViews();
+
+ void Destroy();
+
+ const vk::SurfaceKHR surface;
+ const VKDevice& device;
+
+ UniqueSwapchainKHR swapchain;
+
+ u32 image_count{};
+ std::vector<vk::Image> images;
+ std::vector<UniqueImageView> image_views;
+ std::vector<UniqueFramebuffer> framebuffers;
+ std::vector<VKFence*> fences;
+ std::vector<UniqueSemaphore> present_semaphores;
+
+ u32 image_index{};
+ u32 frame_index{};
+
+ vk::Format image_format{};
+ vk::Extent2D extent{};
+
+ u32 current_width{};
+ u32 current_height{};
+};
+
+} // namespace Vulkan
diff --git a/src/video_core/sampler_cache.cpp b/src/video_core/sampler_cache.cpp
new file mode 100644
index 000000000..53c7ef12d
--- /dev/null
+++ b/src/video_core/sampler_cache.cpp
@@ -0,0 +1,21 @@
+// 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
new file mode 100644
index 000000000..cbe3ad071
--- /dev/null
+++ b/src/video_core/sampler_cache.h
@@ -0,0 +1,60 @@
+// 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/decode.cpp b/src/video_core/shader/decode.cpp
index 740ac3118..a0554c97e 100644
--- a/src/video_core/shader/decode.cpp
+++ b/src/video_core/shader/decode.cpp
@@ -11,6 +11,7 @@
#include "common/common_types.h"
#include "video_core/engines/shader_bytecode.h"
#include "video_core/engines/shader_header.h"
+#include "video_core/shader/node_helper.h"
#include "video_core/shader/shader_ir.h"
namespace VideoCommon::Shader {
@@ -116,6 +117,8 @@ ExitMethod ShaderIR::Scan(u32 begin, u32 end, std::set<u32>& labels) {
// Continue scanning for an exit method.
break;
}
+ default:
+ break;
}
}
return exit_method = ExitMethod::AlwaysReturn;
@@ -165,6 +168,7 @@ u32 ShaderIR::DecodeInstr(NodeBlock& bb, u32 pc) {
{OpCode::Type::Hfma2, &ShaderIR::DecodeHfma2},
{OpCode::Type::Conversion, &ShaderIR::DecodeConversion},
{OpCode::Type::Memory, &ShaderIR::DecodeMemory},
+ {OpCode::Type::Texture, &ShaderIR::DecodeTexture},
{OpCode::Type::FloatSetPredicate, &ShaderIR::DecodeFloatSetPredicate},
{OpCode::Type::IntegerSetPredicate, &ShaderIR::DecodeIntegerSetPredicate},
{OpCode::Type::HalfSetPredicate, &ShaderIR::DecodeHalfSetPredicate},
@@ -205,4 +209,4 @@ u32 ShaderIR::DecodeInstr(NodeBlock& bb, u32 pc) {
return pc + 1;
}
-} // namespace VideoCommon::Shader \ No newline at end of file
+} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/decode/arithmetic.cpp b/src/video_core/shader/decode/arithmetic.cpp
index 3190e2d7c..87d8fecaa 100644
--- a/src/video_core/shader/decode/arithmetic.cpp
+++ b/src/video_core/shader/decode/arithmetic.cpp
@@ -4,7 +4,9 @@
#include "common/assert.h"
#include "common/common_types.h"
+#include "common/logging/log.h"
#include "video_core/engines/shader_bytecode.h"
+#include "video_core/shader/node_helper.h"
#include "video_core/shader/shader_ir.h"
namespace VideoCommon::Shader {
@@ -152,4 +154,4 @@ u32 ShaderIR::DecodeArithmetic(NodeBlock& bb, u32 pc) {
return pc;
}
-} // namespace VideoCommon::Shader \ No newline at end of file
+} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/decode/arithmetic_half.cpp b/src/video_core/shader/decode/arithmetic_half.cpp
index baee89107..b06cbe441 100644
--- a/src/video_core/shader/decode/arithmetic_half.cpp
+++ b/src/video_core/shader/decode/arithmetic_half.cpp
@@ -4,11 +4,14 @@
#include "common/assert.h"
#include "common/common_types.h"
+#include "common/logging/log.h"
#include "video_core/engines/shader_bytecode.h"
+#include "video_core/shader/node_helper.h"
#include "video_core/shader/shader_ir.h"
namespace VideoCommon::Shader {
+using Tegra::Shader::HalfType;
using Tegra::Shader::Instruction;
using Tegra::Shader::OpCode;
@@ -18,48 +21,50 @@ u32 ShaderIR::DecodeArithmeticHalf(NodeBlock& bb, u32 pc) {
if (opcode->get().GetId() == OpCode::Id::HADD2_C ||
opcode->get().GetId() == OpCode::Id::HADD2_R) {
- UNIMPLEMENTED_IF(instr.alu_half.ftz != 0);
+ if (instr.alu_half.ftz != 0) {
+ LOG_WARNING(HW_GPU, "{} FTZ not implemented", opcode->get().GetName());
+ }
}
- UNIMPLEMENTED_IF_MSG(instr.alu_half.saturate != 0, "Half float saturation not implemented");
const bool negate_a =
opcode->get().GetId() != OpCode::Id::HMUL2_R && instr.alu_half.negate_a != 0;
const bool negate_b =
opcode->get().GetId() != OpCode::Id::HMUL2_C && instr.alu_half.negate_b != 0;
- const Node op_a = GetOperandAbsNegHalf(GetRegister(instr.gpr8), instr.alu_half.abs_a, negate_a);
-
- // instr.alu_half.type_a
+ Node op_a = UnpackHalfFloat(GetRegister(instr.gpr8), instr.alu_half.type_a);
+ op_a = GetOperandAbsNegHalf(op_a, instr.alu_half.abs_a, negate_a);
- Node op_b = [&]() {
+ auto [type_b, op_b] = [&]() -> std::tuple<HalfType, Node> {
switch (opcode->get().GetId()) {
case OpCode::Id::HADD2_C:
case OpCode::Id::HMUL2_C:
- return GetConstBuffer(instr.cbuf34.index, instr.cbuf34.GetOffset());
+ return {HalfType::F32, GetConstBuffer(instr.cbuf34.index, instr.cbuf34.GetOffset())};
case OpCode::Id::HADD2_R:
case OpCode::Id::HMUL2_R:
- return GetRegister(instr.gpr20);
+ return {instr.alu_half.type_b, GetRegister(instr.gpr20)};
default:
UNREACHABLE();
- return Immediate(0);
+ return {HalfType::F32, Immediate(0)};
}
}();
- op_b = GetOperandAbsNegHalf(op_b, instr.alu_half.abs_b, negate_b);
+ op_b = UnpackHalfFloat(op_b, type_b);
+ // redeclaration to avoid a bug in clang with reusing local bindings in lambdas
+ Node op_b_alt = GetOperandAbsNegHalf(op_b, instr.alu_half.abs_b, negate_b);
Node value = [&]() {
- MetaHalfArithmetic meta{true, {instr.alu_half_imm.type_a, instr.alu_half.type_b}};
switch (opcode->get().GetId()) {
case OpCode::Id::HADD2_C:
case OpCode::Id::HADD2_R:
- return Operation(OperationCode::HAdd, meta, op_a, op_b);
+ return Operation(OperationCode::HAdd, PRECISE, op_a, op_b_alt);
case OpCode::Id::HMUL2_C:
case OpCode::Id::HMUL2_R:
- return Operation(OperationCode::HMul, meta, op_a, op_b);
+ return Operation(OperationCode::HMul, PRECISE, op_a, op_b_alt);
default:
UNIMPLEMENTED_MSG("Unhandled half float instruction: {}", opcode->get().GetName());
return Immediate(0);
}
}();
+ value = GetSaturatedHalfFloat(value, instr.alu_half.saturate);
value = HalfMerge(GetRegister(instr.gpr0), value, instr.alu_half.merge);
SetRegister(bb, instr.gpr0, value);
@@ -67,4 +72,4 @@ u32 ShaderIR::DecodeArithmeticHalf(NodeBlock& bb, u32 pc) {
return pc;
}
-} // namespace VideoCommon::Shader \ No newline at end of file
+} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/decode/arithmetic_half_immediate.cpp b/src/video_core/shader/decode/arithmetic_half_immediate.cpp
index c2164ba50..7bcf38f23 100644
--- a/src/video_core/shader/decode/arithmetic_half_immediate.cpp
+++ b/src/video_core/shader/decode/arithmetic_half_immediate.cpp
@@ -4,7 +4,9 @@
#include "common/assert.h"
#include "common/common_types.h"
+#include "common/logging/log.h"
#include "video_core/engines/shader_bytecode.h"
+#include "video_core/shader/node_helper.h"
#include "video_core/shader/shader_ir.h"
namespace VideoCommon::Shader {
@@ -17,35 +19,34 @@ u32 ShaderIR::DecodeArithmeticHalfImmediate(NodeBlock& bb, u32 pc) {
const auto opcode = OpCode::Decode(instr);
if (opcode->get().GetId() == OpCode::Id::HADD2_IMM) {
- UNIMPLEMENTED_IF(instr.alu_half_imm.ftz != 0);
+ if (instr.alu_half_imm.ftz != 0) {
+ LOG_WARNING(HW_GPU, "{} FTZ not implemented", opcode->get().GetName());
+ }
} else {
UNIMPLEMENTED_IF(instr.alu_half_imm.precision != Tegra::Shader::HalfPrecision::None);
}
- UNIMPLEMENTED_IF_MSG(instr.alu_half_imm.saturate != 0,
- "Half float immediate saturation not implemented");
- Node op_a = GetRegister(instr.gpr8);
+ Node op_a = UnpackHalfFloat(GetRegister(instr.gpr8), instr.alu_half_imm.type_a);
op_a = GetOperandAbsNegHalf(op_a, instr.alu_half_imm.abs_a, instr.alu_half_imm.negate_a);
const Node op_b = UnpackHalfImmediate(instr, true);
Node value = [&]() {
- MetaHalfArithmetic meta{true, {instr.alu_half_imm.type_a}};
switch (opcode->get().GetId()) {
case OpCode::Id::HADD2_IMM:
- return Operation(OperationCode::HAdd, meta, op_a, op_b);
+ return Operation(OperationCode::HAdd, PRECISE, op_a, op_b);
case OpCode::Id::HMUL2_IMM:
- return Operation(OperationCode::HMul, meta, op_a, op_b);
+ return Operation(OperationCode::HMul, PRECISE, op_a, op_b);
default:
UNREACHABLE();
return Immediate(0);
}
}();
- value = HalfMerge(GetRegister(instr.gpr0), value, instr.alu_half_imm.merge);
+ value = GetSaturatedHalfFloat(value, instr.alu_half_imm.saturate);
+ value = HalfMerge(GetRegister(instr.gpr0), value, instr.alu_half_imm.merge);
SetRegister(bb, instr.gpr0, value);
-
return pc;
}
-} // namespace VideoCommon::Shader \ No newline at end of file
+} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/decode/arithmetic_immediate.cpp b/src/video_core/shader/decode/arithmetic_immediate.cpp
index 0d139c0d2..f1875967c 100644
--- a/src/video_core/shader/decode/arithmetic_immediate.cpp
+++ b/src/video_core/shader/decode/arithmetic_immediate.cpp
@@ -5,6 +5,7 @@
#include "common/assert.h"
#include "common/common_types.h"
#include "video_core/engines/shader_bytecode.h"
+#include "video_core/shader/node_helper.h"
#include "video_core/shader/shader_ir.h"
namespace VideoCommon::Shader {
@@ -49,4 +50,4 @@ u32 ShaderIR::DecodeArithmeticImmediate(NodeBlock& bb, u32 pc) {
return pc;
}
-} // namespace VideoCommon::Shader \ No newline at end of file
+} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/decode/arithmetic_integer.cpp b/src/video_core/shader/decode/arithmetic_integer.cpp
index 9fd4b273e..c8c1a7f40 100644
--- a/src/video_core/shader/decode/arithmetic_integer.cpp
+++ b/src/video_core/shader/decode/arithmetic_integer.cpp
@@ -5,6 +5,7 @@
#include "common/assert.h"
#include "common/common_types.h"
#include "video_core/engines/shader_bytecode.h"
+#include "video_core/shader/node_helper.h"
#include "video_core/shader/shader_ir.h"
namespace VideoCommon::Shader {
diff --git a/src/video_core/shader/decode/arithmetic_integer_immediate.cpp b/src/video_core/shader/decode/arithmetic_integer_immediate.cpp
index 3ed5ccc5a..73880db0e 100644
--- a/src/video_core/shader/decode/arithmetic_integer_immediate.cpp
+++ b/src/video_core/shader/decode/arithmetic_integer_immediate.cpp
@@ -5,6 +5,7 @@
#include "common/assert.h"
#include "common/common_types.h"
#include "video_core/engines/shader_bytecode.h"
+#include "video_core/shader/node_helper.h"
#include "video_core/shader/shader_ir.h"
namespace VideoCommon::Shader {
@@ -93,4 +94,4 @@ void ShaderIR::WriteLogicOperation(NodeBlock& bb, Register dest, LogicOperation
}
}
-} // namespace VideoCommon::Shader \ No newline at end of file
+} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/decode/bfe.cpp b/src/video_core/shader/decode/bfe.cpp
index 6a95dc928..e02bcd097 100644
--- a/src/video_core/shader/decode/bfe.cpp
+++ b/src/video_core/shader/decode/bfe.cpp
@@ -5,6 +5,7 @@
#include "common/assert.h"
#include "common/common_types.h"
#include "video_core/engines/shader_bytecode.h"
+#include "video_core/shader/node_helper.h"
#include "video_core/shader/shader_ir.h"
namespace VideoCommon::Shader {
@@ -46,4 +47,4 @@ u32 ShaderIR::DecodeBfe(NodeBlock& bb, u32 pc) {
return pc;
}
-} // namespace VideoCommon::Shader \ No newline at end of file
+} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/decode/bfi.cpp b/src/video_core/shader/decode/bfi.cpp
index 601d66f1f..8be1119df 100644
--- a/src/video_core/shader/decode/bfi.cpp
+++ b/src/video_core/shader/decode/bfi.cpp
@@ -5,6 +5,7 @@
#include "common/assert.h"
#include "common/common_types.h"
#include "video_core/engines/shader_bytecode.h"
+#include "video_core/shader/node_helper.h"
#include "video_core/shader/shader_ir.h"
namespace VideoCommon::Shader {
@@ -38,4 +39,4 @@ u32 ShaderIR::DecodeBfi(NodeBlock& bb, u32 pc) {
return pc;
}
-} // namespace VideoCommon::Shader \ No newline at end of file
+} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/decode/conversion.cpp b/src/video_core/shader/decode/conversion.cpp
index 55a6fbbf2..4221f0c58 100644
--- a/src/video_core/shader/decode/conversion.cpp
+++ b/src/video_core/shader/decode/conversion.cpp
@@ -5,6 +5,7 @@
#include "common/assert.h"
#include "common/common_types.h"
#include "video_core/engines/shader_bytecode.h"
+#include "video_core/shader/node_helper.h"
#include "video_core/shader/shader_ir.h"
namespace VideoCommon::Shader {
@@ -18,13 +19,29 @@ u32 ShaderIR::DecodeConversion(NodeBlock& bb, u32 pc) {
const auto opcode = OpCode::Decode(instr);
switch (opcode->get().GetId()) {
- case OpCode::Id::I2I_R: {
+ case OpCode::Id::I2I_R:
+ case OpCode::Id::I2I_C:
+ case OpCode::Id::I2I_IMM: {
UNIMPLEMENTED_IF(instr.conversion.selector);
+ UNIMPLEMENTED_IF(instr.conversion.dst_size != Register::Size::Word);
+ UNIMPLEMENTED_IF(instr.alu.saturate_d);
const bool input_signed = instr.conversion.is_input_signed;
const bool output_signed = instr.conversion.is_output_signed;
- Node value = GetRegister(instr.gpr20);
+ Node value = [&]() {
+ switch (opcode->get().GetId()) {
+ case OpCode::Id::I2I_R:
+ return GetRegister(instr.gpr20);
+ case OpCode::Id::I2I_C:
+ return GetConstBuffer(instr.cbuf34.index, instr.cbuf34.GetOffset());
+ case OpCode::Id::I2I_IMM:
+ return Immediate(instr.alu.GetSignedImm20_20());
+ default:
+ UNREACHABLE();
+ return Immediate(0);
+ }
+ }();
value = ConvertIntegerSize(value, instr.conversion.src_size, input_signed);
value = GetOperandAbsNegInteger(value, instr.conversion.abs_a, instr.conversion.negate_a,
@@ -38,17 +55,24 @@ u32 ShaderIR::DecodeConversion(NodeBlock& bb, u32 pc) {
break;
}
case OpCode::Id::I2F_R:
- case OpCode::Id::I2F_C: {
- UNIMPLEMENTED_IF(instr.conversion.dest_size != Register::Size::Word);
+ case OpCode::Id::I2F_C:
+ case OpCode::Id::I2F_IMM: {
+ UNIMPLEMENTED_IF(instr.conversion.dst_size != Register::Size::Word);
UNIMPLEMENTED_IF(instr.conversion.selector);
UNIMPLEMENTED_IF_MSG(instr.generates_cc,
"Condition codes generation in I2F is not implemented");
Node value = [&]() {
- if (instr.is_b_gpr) {
+ switch (opcode->get().GetId()) {
+ case OpCode::Id::I2F_R:
return GetRegister(instr.gpr20);
- } else {
+ case OpCode::Id::I2F_C:
return GetConstBuffer(instr.cbuf34.index, instr.cbuf34.GetOffset());
+ case OpCode::Id::I2F_IMM:
+ return Immediate(instr.alu.GetSignedImm20_20());
+ default:
+ UNREACHABLE();
+ return Immediate(0);
}
}();
const bool input_signed = instr.conversion.is_input_signed;
@@ -62,24 +86,31 @@ u32 ShaderIR::DecodeConversion(NodeBlock& bb, u32 pc) {
break;
}
case OpCode::Id::F2F_R:
- case OpCode::Id::F2F_C: {
- UNIMPLEMENTED_IF(instr.conversion.dest_size != Register::Size::Word);
- UNIMPLEMENTED_IF(instr.conversion.src_size != Register::Size::Word);
+ case OpCode::Id::F2F_C:
+ case OpCode::Id::F2F_IMM: {
+ UNIMPLEMENTED_IF(instr.conversion.f2f.dst_size != Register::Size::Word);
+ UNIMPLEMENTED_IF(instr.conversion.f2f.src_size != Register::Size::Word);
UNIMPLEMENTED_IF_MSG(instr.generates_cc,
"Condition codes generation in F2F is not implemented");
Node value = [&]() {
- if (instr.is_b_gpr) {
+ switch (opcode->get().GetId()) {
+ case OpCode::Id::F2F_R:
return GetRegister(instr.gpr20);
- } else {
+ case OpCode::Id::F2F_C:
return GetConstBuffer(instr.cbuf34.index, instr.cbuf34.GetOffset());
+ case OpCode::Id::F2F_IMM:
+ return GetImmediate19(instr);
+ default:
+ UNREACHABLE();
+ return Immediate(0);
}
}();
value = GetOperandAbsNegFloat(value, instr.conversion.abs_a, instr.conversion.negate_a);
value = [&]() {
- switch (instr.conversion.f2f.rounding) {
+ switch (instr.conversion.f2f.GetRoundingMode()) {
case Tegra::Shader::F2fRoundingOp::None:
return value;
case Tegra::Shader::F2fRoundingOp::Round:
@@ -90,10 +121,11 @@ u32 ShaderIR::DecodeConversion(NodeBlock& bb, u32 pc) {
return Operation(OperationCode::FCeil, PRECISE, value);
case Tegra::Shader::F2fRoundingOp::Trunc:
return Operation(OperationCode::FTrunc, PRECISE, value);
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented F2F rounding mode {}",
+ static_cast<u32>(instr.conversion.f2f.rounding.Value()));
+ return Immediate(0);
}
- UNIMPLEMENTED_MSG("Unimplemented F2F rounding mode {}",
- static_cast<u32>(instr.conversion.f2f.rounding.Value()));
- return Immediate(0);
}();
value = GetSaturatedFloat(value, instr.alu.saturate_d);
@@ -102,15 +134,22 @@ u32 ShaderIR::DecodeConversion(NodeBlock& bb, u32 pc) {
break;
}
case OpCode::Id::F2I_R:
- case OpCode::Id::F2I_C: {
+ case OpCode::Id::F2I_C:
+ case OpCode::Id::F2I_IMM: {
UNIMPLEMENTED_IF(instr.conversion.src_size != Register::Size::Word);
UNIMPLEMENTED_IF_MSG(instr.generates_cc,
"Condition codes generation in F2I is not implemented");
Node value = [&]() {
- if (instr.is_b_gpr) {
+ switch (opcode->get().GetId()) {
+ case OpCode::Id::F2I_R:
return GetRegister(instr.gpr20);
- } else {
+ case OpCode::Id::F2I_C:
return GetConstBuffer(instr.cbuf34.index, instr.cbuf34.GetOffset());
+ case OpCode::Id::F2I_IMM:
+ return GetImmediate19(instr);
+ default:
+ UNREACHABLE();
+ return Immediate(0);
}
}();
@@ -134,7 +173,7 @@ u32 ShaderIR::DecodeConversion(NodeBlock& bb, u32 pc) {
}();
const bool is_signed = instr.conversion.is_output_signed;
value = SignedOperation(OperationCode::ICastFloat, is_signed, PRECISE, value);
- value = ConvertIntegerSize(value, instr.conversion.dest_size, is_signed);
+ value = ConvertIntegerSize(value, instr.conversion.dst_size, is_signed);
SetRegister(bb, instr.gpr0, value);
break;
diff --git a/src/video_core/shader/decode/ffma.cpp b/src/video_core/shader/decode/ffma.cpp
index 0559cc8de..29be25ca3 100644
--- a/src/video_core/shader/decode/ffma.cpp
+++ b/src/video_core/shader/decode/ffma.cpp
@@ -5,6 +5,7 @@
#include "common/assert.h"
#include "common/common_types.h"
#include "video_core/engines/shader_bytecode.h"
+#include "video_core/shader/node_helper.h"
#include "video_core/shader/shader_ir.h"
namespace VideoCommon::Shader {
@@ -56,4 +57,4 @@ u32 ShaderIR::DecodeFfma(NodeBlock& bb, u32 pc) {
return pc;
}
-} // namespace VideoCommon::Shader \ No newline at end of file
+} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/decode/float_set.cpp b/src/video_core/shader/decode/float_set.cpp
index 1bd6755dd..f5013e44a 100644
--- a/src/video_core/shader/decode/float_set.cpp
+++ b/src/video_core/shader/decode/float_set.cpp
@@ -5,6 +5,7 @@
#include "common/assert.h"
#include "common/common_types.h"
#include "video_core/engines/shader_bytecode.h"
+#include "video_core/shader/node_helper.h"
#include "video_core/shader/shader_ir.h"
namespace VideoCommon::Shader {
@@ -55,4 +56,4 @@ u32 ShaderIR::DecodeFloatSet(NodeBlock& bb, u32 pc) {
return pc;
}
-} // namespace VideoCommon::Shader \ No newline at end of file
+} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/decode/float_set_predicate.cpp b/src/video_core/shader/decode/float_set_predicate.cpp
index 9285b8d05..2323052b0 100644
--- a/src/video_core/shader/decode/float_set_predicate.cpp
+++ b/src/video_core/shader/decode/float_set_predicate.cpp
@@ -5,6 +5,7 @@
#include "common/assert.h"
#include "common/common_types.h"
#include "video_core/engines/shader_bytecode.h"
+#include "video_core/shader/node_helper.h"
#include "video_core/shader/shader_ir.h"
namespace VideoCommon::Shader {
@@ -53,4 +54,4 @@ u32 ShaderIR::DecodeFloatSetPredicate(NodeBlock& bb, u32 pc) {
return pc;
}
-} // namespace VideoCommon::Shader \ No newline at end of file
+} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/decode/half_set.cpp b/src/video_core/shader/decode/half_set.cpp
index 748368555..48ca7a4af 100644
--- a/src/video_core/shader/decode/half_set.cpp
+++ b/src/video_core/shader/decode/half_set.cpp
@@ -6,7 +6,9 @@
#include "common/assert.h"
#include "common/common_types.h"
+#include "common/logging/log.h"
#include "video_core/engines/shader_bytecode.h"
+#include "video_core/shader/node_helper.h"
#include "video_core/shader/shader_ir.h"
namespace VideoCommon::Shader {
@@ -18,11 +20,13 @@ u32 ShaderIR::DecodeHalfSet(NodeBlock& bb, u32 pc) {
const Instruction instr = {program_code[pc]};
const auto opcode = OpCode::Decode(instr);
- UNIMPLEMENTED_IF(instr.hset2.ftz != 0);
+ if (instr.hset2.ftz != 0) {
+ LOG_WARNING(HW_GPU, "{} FTZ not implemented", opcode->get().GetName());
+ }
+
+ Node op_a = UnpackHalfFloat(GetRegister(instr.gpr8), instr.hset2.type_a);
+ op_a = GetOperandAbsNegHalf(op_a, instr.hset2.abs_a, instr.hset2.negate_a);
- // instr.hset2.type_a
- // instr.hset2.type_b
- Node op_a = GetRegister(instr.gpr8);
Node op_b = [&]() {
switch (opcode->get().GetId()) {
case OpCode::Id::HSET2_R:
@@ -32,14 +36,12 @@ u32 ShaderIR::DecodeHalfSet(NodeBlock& bb, u32 pc) {
return Immediate(0);
}
}();
-
- op_a = GetOperandAbsNegHalf(op_a, instr.hset2.abs_a, instr.hset2.negate_a);
+ op_b = UnpackHalfFloat(op_b, instr.hset2.type_b);
op_b = GetOperandAbsNegHalf(op_b, instr.hset2.abs_b, instr.hset2.negate_b);
const Node second_pred = GetPredicate(instr.hset2.pred39, instr.hset2.neg_pred);
- MetaHalfArithmetic meta{false, {instr.hset2.type_a, instr.hset2.type_b}};
- const Node comparison_pair = GetPredicateComparisonHalf(instr.hset2.cond, meta, op_a, op_b);
+ const Node comparison_pair = GetPredicateComparisonHalf(instr.hset2.cond, op_a, op_b);
const OperationCode combiner = GetPredicateCombiner(instr.hset2.op);
@@ -64,4 +66,4 @@ u32 ShaderIR::DecodeHalfSet(NodeBlock& bb, u32 pc) {
return pc;
}
-} // namespace VideoCommon::Shader \ No newline at end of file
+} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/decode/half_set_predicate.cpp b/src/video_core/shader/decode/half_set_predicate.cpp
index e68512692..d59d15bd8 100644
--- a/src/video_core/shader/decode/half_set_predicate.cpp
+++ b/src/video_core/shader/decode/half_set_predicate.cpp
@@ -5,6 +5,7 @@
#include "common/assert.h"
#include "common/common_types.h"
#include "video_core/engines/shader_bytecode.h"
+#include "video_core/shader/node_helper.h"
#include "video_core/shader/shader_ir.h"
namespace VideoCommon::Shader {
@@ -19,10 +20,10 @@ u32 ShaderIR::DecodeHalfSetPredicate(NodeBlock& bb, u32 pc) {
UNIMPLEMENTED_IF(instr.hsetp2.ftz != 0);
- Node op_a = GetRegister(instr.gpr8);
+ Node op_a = UnpackHalfFloat(GetRegister(instr.gpr8), instr.hsetp2.type_a);
op_a = GetOperandAbsNegHalf(op_a, instr.hsetp2.abs_a, instr.hsetp2.negate_a);
- const Node op_b = [&]() {
+ Node op_b = [&]() {
switch (opcode->get().GetId()) {
case OpCode::Id::HSETP2_R:
return GetOperandAbsNegHalf(GetRegister(instr.gpr20), instr.hsetp2.abs_a,
@@ -32,6 +33,7 @@ u32 ShaderIR::DecodeHalfSetPredicate(NodeBlock& bb, u32 pc) {
return Immediate(0);
}
}();
+ op_b = UnpackHalfFloat(op_b, instr.hsetp2.type_b);
// We can't use the constant predicate as destination.
ASSERT(instr.hsetp2.pred3 != static_cast<u64>(Pred::UnusedIndex));
@@ -42,8 +44,7 @@ u32 ShaderIR::DecodeHalfSetPredicate(NodeBlock& bb, u32 pc) {
const OperationCode pair_combiner =
instr.hsetp2.h_and ? OperationCode::LogicalAll2 : OperationCode::LogicalAny2;
- MetaHalfArithmetic meta = {false, {instr.hsetp2.type_a, instr.hsetp2.type_b}};
- const Node comparison = GetPredicateComparisonHalf(instr.hsetp2.cond, meta, op_a, op_b);
+ const Node comparison = GetPredicateComparisonHalf(instr.hsetp2.cond, op_a, op_b);
const Node first_pred = Operation(pair_combiner, comparison);
// Set the primary predicate to the result of Predicate OP SecondPredicate
@@ -59,4 +60,4 @@ u32 ShaderIR::DecodeHalfSetPredicate(NodeBlock& bb, u32 pc) {
return pc;
}
-} // namespace VideoCommon::Shader \ No newline at end of file
+} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/decode/hfma2.cpp b/src/video_core/shader/decode/hfma2.cpp
index 7a07c5ec6..c3bcf1ae9 100644
--- a/src/video_core/shader/decode/hfma2.cpp
+++ b/src/video_core/shader/decode/hfma2.cpp
@@ -7,6 +7,7 @@
#include "common/assert.h"
#include "common/common_types.h"
#include "video_core/engines/shader_bytecode.h"
+#include "video_core/shader/node_helper.h"
#include "video_core/shader/shader_ir.h"
namespace VideoCommon::Shader {
@@ -27,10 +28,6 @@ u32 ShaderIR::DecodeHfma2(NodeBlock& bb, u32 pc) {
}
constexpr auto identity = HalfType::H0_H1;
-
- const HalfType type_a = instr.hfma2.type_a;
- const Node op_a = GetRegister(instr.gpr8);
-
bool neg_b{}, neg_c{};
auto [saturate, type_b, op_b, type_c,
op_c] = [&]() -> std::tuple<bool, HalfType, Node, HalfType, Node> {
@@ -38,15 +35,14 @@ u32 ShaderIR::DecodeHfma2(NodeBlock& bb, u32 pc) {
case OpCode::Id::HFMA2_CR:
neg_b = instr.hfma2.negate_b;
neg_c = instr.hfma2.negate_c;
- return {instr.hfma2.saturate, instr.hfma2.type_b,
+ return {instr.hfma2.saturate, HalfType::F32,
GetConstBuffer(instr.cbuf34.index, instr.cbuf34.GetOffset()),
instr.hfma2.type_reg39, GetRegister(instr.gpr39)};
case OpCode::Id::HFMA2_RC:
neg_b = instr.hfma2.negate_b;
neg_c = instr.hfma2.negate_c;
return {instr.hfma2.saturate, instr.hfma2.type_reg39, GetRegister(instr.gpr39),
- instr.hfma2.type_b,
- GetConstBuffer(instr.cbuf34.index, instr.cbuf34.GetOffset())};
+ HalfType::F32, GetConstBuffer(instr.cbuf34.index, instr.cbuf34.GetOffset())};
case OpCode::Id::HFMA2_RR:
neg_b = instr.hfma2.rr.negate_b;
neg_c = instr.hfma2.rr.negate_c;
@@ -60,13 +56,13 @@ u32 ShaderIR::DecodeHfma2(NodeBlock& bb, u32 pc) {
return {false, identity, Immediate(0), identity, Immediate(0)};
}
}();
- UNIMPLEMENTED_IF_MSG(saturate, "HFMA2 saturation is not implemented");
- op_b = GetOperandAbsNegHalf(op_b, false, neg_b);
- op_c = GetOperandAbsNegHalf(op_c, false, neg_c);
+ const Node op_a = UnpackHalfFloat(GetRegister(instr.gpr8), instr.hfma2.type_a);
+ op_b = GetOperandAbsNegHalf(UnpackHalfFloat(op_b, type_b), false, neg_b);
+ op_c = GetOperandAbsNegHalf(UnpackHalfFloat(op_c, type_c), false, neg_c);
- MetaHalfArithmetic meta{true, {type_a, type_b, type_c}};
- Node value = Operation(OperationCode::HFma, meta, op_a, op_b, op_c);
+ Node value = Operation(OperationCode::HFma, PRECISE, op_a, op_b, op_c);
+ value = GetSaturatedHalfFloat(value, saturate);
value = HalfMerge(GetRegister(instr.gpr0), value, instr.hfma2.merge);
SetRegister(bb, instr.gpr0, value);
@@ -74,4 +70,4 @@ u32 ShaderIR::DecodeHfma2(NodeBlock& bb, u32 pc) {
return pc;
}
-} // namespace VideoCommon::Shader \ No newline at end of file
+} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/decode/integer_set.cpp b/src/video_core/shader/decode/integer_set.cpp
index a3bf17eba..46e3d5905 100644
--- a/src/video_core/shader/decode/integer_set.cpp
+++ b/src/video_core/shader/decode/integer_set.cpp
@@ -2,9 +2,9 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include "common/assert.h"
#include "common/common_types.h"
#include "video_core/engines/shader_bytecode.h"
+#include "video_core/shader/node_helper.h"
#include "video_core/shader/shader_ir.h"
namespace VideoCommon::Shader {
@@ -47,4 +47,4 @@ u32 ShaderIR::DecodeIntegerSet(NodeBlock& bb, u32 pc) {
return pc;
}
-} // namespace VideoCommon::Shader \ No newline at end of file
+} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/decode/integer_set_predicate.cpp b/src/video_core/shader/decode/integer_set_predicate.cpp
index aad836d24..dd20775d7 100644
--- a/src/video_core/shader/decode/integer_set_predicate.cpp
+++ b/src/video_core/shader/decode/integer_set_predicate.cpp
@@ -5,6 +5,7 @@
#include "common/assert.h"
#include "common/common_types.h"
#include "video_core/engines/shader_bytecode.h"
+#include "video_core/shader/node_helper.h"
#include "video_core/shader/shader_ir.h"
namespace VideoCommon::Shader {
@@ -50,4 +51,4 @@ u32 ShaderIR::DecodeIntegerSetPredicate(NodeBlock& bb, u32 pc) {
return pc;
}
-} // namespace VideoCommon::Shader \ No newline at end of file
+} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/decode/memory.cpp b/src/video_core/shader/decode/memory.cpp
index 38f01ca50..80fc0ccfc 100644
--- a/src/video_core/shader/decode/memory.cpp
+++ b/src/video_core/shader/decode/memory.cpp
@@ -8,7 +8,9 @@
#include "common/assert.h"
#include "common/common_types.h"
+#include "common/logging/log.h"
#include "video_core/engines/shader_bytecode.h"
+#include "video_core/shader/node_helper.h"
#include "video_core/shader/shader_ir.h"
namespace VideoCommon::Shader {
@@ -17,24 +19,23 @@ using Tegra::Shader::Attribute;
using Tegra::Shader::Instruction;
using Tegra::Shader::OpCode;
using Tegra::Shader::Register;
-using Tegra::Shader::TextureMiscMode;
-using Tegra::Shader::TextureProcessMode;
-using Tegra::Shader::TextureType;
-static std::size_t GetCoordCount(TextureType texture_type) {
- switch (texture_type) {
- case TextureType::Texture1D:
+namespace {
+u32 GetUniformTypeElementsCount(Tegra::Shader::UniformType uniform_type) {
+ switch (uniform_type) {
+ case Tegra::Shader::UniformType::Single:
return 1;
- case TextureType::Texture2D:
+ case Tegra::Shader::UniformType::Double:
return 2;
- case TextureType::Texture3D:
- case TextureType::TextureCube:
- return 3;
+ case Tegra::Shader::UniformType::Quad:
+ case Tegra::Shader::UniformType::UnsignedQuad:
+ return 4;
default:
- UNIMPLEMENTED_MSG("Unhandled texture type: {}", static_cast<u32>(texture_type));
- return 0;
+ UNIMPLEMENTED_MSG("Unimplemented size={}!", static_cast<u32>(uniform_type));
+ return 1;
}
}
+} // namespace
u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) {
const Instruction instr = {program_code[pc]};
@@ -47,17 +48,20 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) {
"Indirect attribute loads are not supported");
UNIMPLEMENTED_IF_MSG((instr.attribute.fmt20.immediate.Value() % sizeof(u32)) != 0,
"Unaligned attribute loads are not supported");
+ UNIMPLEMENTED_IF_MSG(instr.attribute.fmt20.IsPhysical() &&
+ instr.attribute.fmt20.size != Tegra::Shader::AttributeSize::Word,
+ "Non-32 bits PHYS reads are not implemented");
- Tegra::Shader::IpaMode input_mode{Tegra::Shader::IpaInterpMode::Pass,
- Tegra::Shader::IpaSampleMode::Default};
+ const Node buffer{GetRegister(instr.gpr39)};
u64 next_element = instr.attribute.fmt20.element;
auto next_index = static_cast<u64>(instr.attribute.fmt20.index.Value());
const auto LoadNextElement = [&](u32 reg_offset) {
- const Node buffer = GetRegister(instr.gpr39);
- const Node attribute = GetInputAttribute(static_cast<Attribute::Index>(next_index),
- next_element, input_mode, buffer);
+ const Node attribute{instr.attribute.fmt20.IsPhysical()
+ ? GetPhysicalInputAttribute(instr.gpr8, buffer)
+ : GetInputAttribute(static_cast<Attribute::Index>(next_index),
+ next_element, buffer)};
SetRegister(bb, instr.gpr0.Value() + reg_offset, attribute);
@@ -103,8 +107,8 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) {
break;
}
case OpCode::Id::LD_L: {
- UNIMPLEMENTED_IF_MSG(instr.ld_l.unknown == 1, "LD_L Unhandled mode: {}",
- static_cast<u32>(instr.ld_l.unknown.Value()));
+ LOG_DEBUG(HW_GPU, "LD_L cache management mode: {}",
+ static_cast<u64>(instr.ld_l.unknown.Value()));
const auto GetLmem = [&](s32 offset) {
ASSERT(offset % 4 == 0);
@@ -143,47 +147,30 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) {
}
break;
}
+ case OpCode::Id::LD:
case OpCode::Id::LDG: {
- const u32 count = [&]() {
- switch (instr.ldg.type) {
- case Tegra::Shader::UniformType::Single:
- return 1;
- case Tegra::Shader::UniformType::Double:
- return 2;
- case Tegra::Shader::UniformType::Quad:
- case Tegra::Shader::UniformType::UnsignedQuad:
- return 4;
+ const auto type = [instr, &opcode]() -> Tegra::Shader::UniformType {
+ switch (opcode->get().GetId()) {
+ case OpCode::Id::LD:
+ UNIMPLEMENTED_IF_MSG(!instr.generic.extended, "Unextended LD is not implemented");
+ return instr.generic.type;
+ case OpCode::Id::LDG:
+ return instr.ldg.type;
default:
- UNIMPLEMENTED_MSG("Unimplemented LDG size!");
- return 1;
+ UNREACHABLE();
+ return {};
}
}();
- const Node addr_register = GetRegister(instr.gpr8);
- const Node base_address =
- TrackCbuf(addr_register, global_code, static_cast<s64>(global_code.size()));
- const auto cbuf = std::get_if<CbufNode>(base_address);
- ASSERT(cbuf != nullptr);
- const auto cbuf_offset_imm = std::get_if<ImmediateNode>(cbuf->GetOffset());
- ASSERT(cbuf_offset_imm != nullptr);
- const auto cbuf_offset = cbuf_offset_imm->GetValue();
-
- bb.push_back(Comment(
- fmt::format("Base address is c[0x{:x}][0x{:x}]", cbuf->GetIndex(), cbuf_offset)));
-
- const GlobalMemoryBase descriptor{cbuf->GetIndex(), cbuf_offset};
- used_global_memory_bases.insert(descriptor);
-
- const Node immediate_offset =
- Immediate(static_cast<u32>(instr.ldg.immediate_offset.Value()));
- const Node base_real_address =
- Operation(OperationCode::UAdd, NO_PRECISE, immediate_offset, addr_register);
+ const auto [real_address_base, base_address, descriptor] =
+ TrackAndGetGlobalMemory(bb, instr, false);
+ const u32 count = GetUniformTypeElementsCount(type);
for (u32 i = 0; i < count; ++i) {
const Node it_offset = Immediate(i * 4);
const Node real_address =
- Operation(OperationCode::UAdd, NO_PRECISE, base_real_address, it_offset);
- const Node gmem = StoreNode(GmemNode(real_address, base_address, descriptor));
+ Operation(OperationCode::UAdd, NO_PRECISE, real_address_base, it_offset);
+ const Node gmem = MakeNode<GmemNode>(real_address, base_address, descriptor);
SetTemporal(bb, i, gmem);
}
@@ -223,8 +210,8 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) {
break;
}
case OpCode::Id::ST_L: {
- UNIMPLEMENTED_IF_MSG(instr.st_l.unknown == 0, "ST_L Unhandled mode: {}",
- static_cast<u32>(instr.st_l.unknown.Value()));
+ LOG_DEBUG(HW_GPU, "ST_L cache management mode: {}",
+ static_cast<u64>(instr.st_l.cache_management.Value()));
const auto GetLmemAddr = [&](s32 offset) {
ASSERT(offset % 4 == 0);
@@ -247,192 +234,54 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) {
}
break;
}
- case OpCode::Id::TEX: {
- UNIMPLEMENTED_IF_MSG(instr.tex.UsesMiscMode(TextureMiscMode::AOFFI),
- "AOFFI is not implemented");
-
- if (instr.tex.UsesMiscMode(TextureMiscMode::NODEP)) {
- LOG_WARNING(HW_GPU, "TEX.NODEP implementation is incomplete");
- }
-
- const TextureType texture_type{instr.tex.texture_type};
- const bool is_array = instr.tex.array != 0;
- const bool depth_compare = instr.tex.UsesMiscMode(TextureMiscMode::DC);
- const auto process_mode = instr.tex.GetTextureProcessMode();
- WriteTexInstructionFloat(
- bb, instr, GetTexCode(instr, texture_type, process_mode, depth_compare, is_array));
- break;
- }
- case OpCode::Id::TEXS: {
- const TextureType texture_type{instr.texs.GetTextureType()};
- const bool is_array{instr.texs.IsArrayTexture()};
- const bool depth_compare = instr.texs.UsesMiscMode(TextureMiscMode::DC);
- const auto process_mode = instr.texs.GetTextureProcessMode();
-
- if (instr.texs.UsesMiscMode(TextureMiscMode::NODEP)) {
- LOG_WARNING(HW_GPU, "TEXS.NODEP implementation is incomplete");
- }
-
- const Node4 components =
- GetTexsCode(instr, texture_type, process_mode, depth_compare, is_array);
-
- if (instr.texs.fp32_flag) {
- WriteTexsInstructionFloat(bb, instr, components);
- } else {
- WriteTexsInstructionHalfFloat(bb, instr, components);
- }
- break;
- }
- case OpCode::Id::TLD4: {
- ASSERT(instr.tld4.array == 0);
- UNIMPLEMENTED_IF_MSG(instr.tld4.UsesMiscMode(TextureMiscMode::AOFFI),
- "AOFFI is not implemented");
- UNIMPLEMENTED_IF_MSG(instr.tld4.UsesMiscMode(TextureMiscMode::NDV),
- "NDV is not implemented");
- UNIMPLEMENTED_IF_MSG(instr.tld4.UsesMiscMode(TextureMiscMode::PTP),
- "PTP is not implemented");
-
- if (instr.tld4.UsesMiscMode(TextureMiscMode::NODEP)) {
- LOG_WARNING(HW_GPU, "TLD4.NODEP implementation is incomplete");
- }
-
- const auto texture_type = instr.tld4.texture_type.Value();
- const bool depth_compare = instr.tld4.UsesMiscMode(TextureMiscMode::DC);
- const bool is_array = instr.tld4.array != 0;
- WriteTexInstructionFloat(bb, instr,
- GetTld4Code(instr, texture_type, depth_compare, is_array));
- break;
- }
- case OpCode::Id::TLD4S: {
- UNIMPLEMENTED_IF_MSG(instr.tld4s.UsesMiscMode(TextureMiscMode::AOFFI),
- "AOFFI is not implemented");
- if (instr.tld4s.UsesMiscMode(TextureMiscMode::NODEP)) {
- LOG_WARNING(HW_GPU, "TLD4S.NODEP implementation is incomplete");
- }
-
- const bool depth_compare = instr.tld4s.UsesMiscMode(TextureMiscMode::DC);
- const Node op_a = GetRegister(instr.gpr8);
- const Node op_b = GetRegister(instr.gpr20);
-
- // TODO(Subv): Figure out how the sampler type is encoded in the TLD4S instruction.
- std::vector<Node> coords;
- if (depth_compare) {
- // Note: TLD4S coordinate encoding works just like TEXS's
- const Node op_y = GetRegister(instr.gpr8.Value() + 1);
- coords.push_back(op_a);
- coords.push_back(op_y);
- coords.push_back(op_b);
- } else {
- coords.push_back(op_a);
- coords.push_back(op_b);
- }
- std::vector<Node> extras;
- extras.push_back(Immediate(static_cast<u32>(instr.tld4s.component)));
-
- const auto& sampler =
- GetSampler(instr.sampler, TextureType::Texture2D, false, depth_compare);
-
- Node4 values;
- for (u32 element = 0; element < values.size(); ++element) {
- auto coords_copy = coords;
- MetaTexture meta{sampler, {}, {}, extras, element};
- values[element] = Operation(OperationCode::TextureGather, meta, std::move(coords_copy));
- }
-
- WriteTexsInstructionFloat(bb, instr, values);
- break;
- }
- case OpCode::Id::TXQ: {
- if (instr.txq.UsesMiscMode(TextureMiscMode::NODEP)) {
- LOG_WARNING(HW_GPU, "TXQ.NODEP implementation is incomplete");
- }
-
- // TODO: The new commits on the texture refactor, change the way samplers work.
- // Sadly, not all texture instructions specify the type of texture their sampler
- // uses. This must be fixed at a later instance.
- const auto& sampler =
- GetSampler(instr.sampler, Tegra::Shader::TextureType::Texture2D, false, false);
-
- u32 indexer = 0;
- switch (instr.txq.query_type) {
- case Tegra::Shader::TextureQueryType::Dimension: {
- for (u32 element = 0; element < 4; ++element) {
- if (!instr.txq.IsComponentEnabled(element)) {
- continue;
- }
- MetaTexture meta{sampler, {}, {}, {}, element};
- const Node value =
- Operation(OperationCode::TextureQueryDimensions, meta, GetRegister(instr.gpr8));
- SetTemporal(bb, indexer++, value);
- }
- for (u32 i = 0; i < indexer; ++i) {
- SetRegister(bb, instr.gpr0.Value() + i, GetTemporal(i));
+ case OpCode::Id::ST:
+ case OpCode::Id::STG: {
+ const auto type = [instr, &opcode]() -> Tegra::Shader::UniformType {
+ switch (opcode->get().GetId()) {
+ case OpCode::Id::ST:
+ UNIMPLEMENTED_IF_MSG(!instr.generic.extended, "Unextended ST is not implemented");
+ return instr.generic.type;
+ case OpCode::Id::STG:
+ return instr.stg.type;
+ default:
+ UNREACHABLE();
+ return {};
}
- break;
- }
- default:
- UNIMPLEMENTED_MSG("Unhandled texture query type: {}",
- static_cast<u32>(instr.txq.query_type.Value()));
- }
- break;
- }
- case OpCode::Id::TMML: {
- UNIMPLEMENTED_IF_MSG(instr.tmml.UsesMiscMode(Tegra::Shader::TextureMiscMode::NDV),
- "NDV is not implemented");
-
- if (instr.tmml.UsesMiscMode(TextureMiscMode::NODEP)) {
- LOG_WARNING(HW_GPU, "TMML.NODEP implementation is incomplete");
- }
+ }();
- auto texture_type = instr.tmml.texture_type.Value();
- const bool is_array = instr.tmml.array != 0;
- const auto& sampler = GetSampler(instr.sampler, texture_type, is_array, false);
+ const auto [real_address_base, base_address, descriptor] =
+ TrackAndGetGlobalMemory(bb, instr, true);
- std::vector<Node> coords;
+ // Encode in temporary registers like this: real_base_address, {registers_to_be_written...}
+ SetTemporal(bb, 0, real_address_base);
- // 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<u32>(texture_type));
-
- // 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));
- texture_type = TextureType::Texture2D;
+ const u32 count = GetUniformTypeElementsCount(type);
+ for (u32 i = 0; i < count; ++i) {
+ SetTemporal(bb, i + 1, GetRegister(instr.gpr0.Value() + i));
}
+ for (u32 i = 0; i < count; ++i) {
+ const Node it_offset = Immediate(i * 4);
+ const Node real_address =
+ Operation(OperationCode::UAdd, NO_PRECISE, real_address_base, it_offset);
+ const Node gmem = MakeNode<GmemNode>(real_address, base_address, descriptor);
- for (u32 element = 0; element < 2; ++element) {
- auto params = coords;
- MetaTexture meta{sampler, {}, {}, {}, element};
- const Node value = Operation(OperationCode::TextureQueryLod, meta, std::move(params));
- SetTemporal(bb, element, value);
+ bb.push_back(Operation(OperationCode::Assign, gmem, GetTemporal(i + 1)));
}
- for (u32 element = 0; element < 2; ++element) {
- SetRegister(bb, instr.gpr0.Value() + element, GetTemporal(element));
- }
-
break;
}
- case OpCode::Id::TLDS: {
- const Tegra::Shader::TextureType texture_type{instr.tlds.GetTextureType()};
- const bool is_array{instr.tlds.IsArrayTexture()};
+ case OpCode::Id::AL2P: {
+ // Ignore al2p.direction since we don't care about it.
- UNIMPLEMENTED_IF_MSG(instr.tlds.UsesMiscMode(TextureMiscMode::AOFFI),
- "AOFFI is not implemented");
- UNIMPLEMENTED_IF_MSG(instr.tlds.UsesMiscMode(TextureMiscMode::MZ), "MZ is not implemented");
+ // Calculate emulation fake physical address.
+ const Node fixed_address{Immediate(static_cast<u32>(instr.al2p.address))};
+ const Node reg{GetRegister(instr.gpr8)};
+ const Node fake_address{Operation(OperationCode::IAdd, NO_PRECISE, reg, fixed_address)};
- if (instr.tlds.UsesMiscMode(TextureMiscMode::NODEP)) {
- LOG_WARNING(HW_GPU, "TLDS.NODEP implementation is incomplete");
- }
+ // Set the fake address to target register.
+ SetRegister(bb, instr.gpr0, fake_address);
- WriteTexsInstructionFloat(bb, instr, GetTldsCode(instr, texture_type, is_array));
+ // Signal the shader IR to declare all possible attributes and varyings
+ uses_physical_attributes = true;
break;
}
default:
@@ -442,291 +291,36 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) {
return pc;
}
-const Sampler& ShaderIR::GetSampler(const Tegra::Shader::Sampler& sampler, TextureType type,
- bool is_array, bool is_shadow) {
- const auto offset = static_cast<std::size_t>(sampler.index.Value());
-
- // If this sampler has already been used, return the existing mapping.
- const auto itr =
- std::find_if(used_samplers.begin(), used_samplers.end(),
- [&](const Sampler& entry) { return entry.GetOffset() == offset; });
- if (itr != used_samplers.end()) {
- ASSERT(itr->GetType() == type && itr->IsArray() == is_array &&
- itr->IsShadow() == is_shadow);
- return *itr;
- }
+std::tuple<Node, Node, GlobalMemoryBase> ShaderIR::TrackAndGetGlobalMemory(NodeBlock& bb,
+ Instruction instr,
+ bool is_write) {
+ const auto addr_register{GetRegister(instr.gmem.gpr)};
+ const auto immediate_offset{static_cast<u32>(instr.gmem.offset)};
- // Otherwise create a new mapping for this sampler
- const std::size_t next_index = used_samplers.size();
- const Sampler entry{offset, next_index, type, is_array, is_shadow};
- return *used_samplers.emplace(entry).first;
-}
+ const Node base_address{
+ TrackCbuf(addr_register, global_code, static_cast<s64>(global_code.size()))};
+ const auto cbuf = std::get_if<CbufNode>(&*base_address);
+ ASSERT(cbuf != nullptr);
+ const auto cbuf_offset_imm = std::get_if<ImmediateNode>(&*cbuf->GetOffset());
+ ASSERT(cbuf_offset_imm != nullptr);
+ const auto cbuf_offset = cbuf_offset_imm->GetValue();
-void ShaderIR::WriteTexInstructionFloat(NodeBlock& bb, Instruction instr, const Node4& components) {
- u32 dest_elem = 0;
- for (u32 elem = 0; elem < 4; ++elem) {
- if (!instr.tex.IsComponentEnabled(elem)) {
- // Skip disabled components
- continue;
- }
- SetTemporal(bb, dest_elem++, components[elem]);
- }
- // After writing values in temporals, move them to the real registers
- for (u32 i = 0; i < dest_elem; ++i) {
- SetRegister(bb, instr.gpr0.Value() + i, GetTemporal(i));
- }
-}
-
-void ShaderIR::WriteTexsInstructionFloat(NodeBlock& bb, Instruction instr,
- const Node4& components) {
- // TEXS has two destination registers and a swizzle. The first two elements in the swizzle
- // go into gpr0+0 and gpr0+1, and the rest goes into gpr28+0 and gpr28+1
-
- u32 dest_elem = 0;
- for (u32 component = 0; component < 4; ++component) {
- if (!instr.texs.IsComponentEnabled(component))
- continue;
- SetTemporal(bb, dest_elem++, components[component]);
- }
+ bb.push_back(
+ Comment(fmt::format("Base address is c[0x{:x}][0x{:x}]", cbuf->GetIndex(), cbuf_offset)));
- for (u32 i = 0; i < dest_elem; ++i) {
- if (i < 2) {
- // Write the first two swizzle components to gpr0 and gpr0+1
- SetRegister(bb, instr.gpr0.Value() + i % 2, GetTemporal(i));
- } else {
- ASSERT(instr.texs.HasTwoDestinations());
- // Write the rest of the swizzle components to gpr28 and gpr28+1
- SetRegister(bb, instr.gpr28.Value() + i % 2, GetTemporal(i));
- }
+ const GlobalMemoryBase descriptor{cbuf->GetIndex(), cbuf_offset};
+ const auto& [entry, is_new] = used_global_memory.try_emplace(descriptor);
+ auto& usage = entry->second;
+ if (is_write) {
+ usage.is_written = true;
+ } else {
+ usage.is_read = true;
}
-}
-
-void ShaderIR::WriteTexsInstructionHalfFloat(NodeBlock& bb, Instruction instr,
- const Node4& components) {
- // TEXS.F16 destionation registers are packed in two registers in pairs (just like any half
- // float instruction).
-
- Node4 values;
- u32 dest_elem = 0;
- for (u32 component = 0; component < 4; ++component) {
- if (!instr.texs.IsComponentEnabled(component))
- continue;
- values[dest_elem++] = components[component];
- }
- if (dest_elem == 0)
- return;
-
- std::generate(values.begin() + dest_elem, values.end(), [&]() { return Immediate(0); });
-
- const Node first_value = Operation(OperationCode::HPack2, values[0], values[1]);
- if (dest_elem <= 2) {
- SetRegister(bb, instr.gpr0, first_value);
- return;
- }
-
- SetTemporal(bb, 0, first_value);
- SetTemporal(bb, 1, Operation(OperationCode::HPack2, values[2], values[3]));
-
- SetRegister(bb, instr.gpr0, GetTemporal(0));
- SetRegister(bb, instr.gpr28, GetTemporal(1));
-}
-Node4 ShaderIR::GetTextureCode(Instruction instr, TextureType texture_type,
- TextureProcessMode process_mode, std::vector<Node> coords,
- Node array, Node depth_compare, u32 bias_offset) {
- const bool is_array = array;
- const bool is_shadow = depth_compare;
-
- UNIMPLEMENTED_IF_MSG((texture_type == TextureType::Texture3D && (is_array || is_shadow)) ||
- (texture_type == TextureType::TextureCube && is_array && is_shadow),
- "This method is not supported.");
-
- const auto& sampler = GetSampler(instr.sampler, texture_type, is_array, is_shadow);
-
- const bool lod_needed = process_mode == TextureProcessMode::LZ ||
- process_mode == TextureProcessMode::LL ||
- process_mode == TextureProcessMode::LLA;
-
- // LOD selection (either via bias or explicit textureLod) not supported in GL for
- // sampler2DArrayShadow and samplerCubeArrayShadow.
- const bool gl_lod_supported =
- !((texture_type == Tegra::Shader::TextureType::Texture2D && is_array && is_shadow) ||
- (texture_type == Tegra::Shader::TextureType::TextureCube && is_array && is_shadow));
-
- const OperationCode read_method =
- lod_needed && gl_lod_supported ? OperationCode::TextureLod : OperationCode::Texture;
-
- UNIMPLEMENTED_IF(process_mode != TextureProcessMode::None && !gl_lod_supported);
-
- std::vector<Node> extras;
- if (process_mode != TextureProcessMode::None && gl_lod_supported) {
- if (process_mode == TextureProcessMode::LZ) {
- extras.push_back(Immediate(0.0f));
- } else {
- // If present, lod or bias are always stored in the register indexed by the gpr20
- // field with an offset depending on the usage of the other registers
- extras.push_back(GetRegister(instr.gpr20.Value() + bias_offset));
- }
- }
-
- Node4 values;
- for (u32 element = 0; element < values.size(); ++element) {
- auto copy_coords = coords;
- MetaTexture meta{sampler, array, depth_compare, extras, element};
- values[element] = Operation(read_method, meta, std::move(copy_coords));
- }
-
- return values;
-}
-
-Node4 ShaderIR::GetTexCode(Instruction instr, TextureType texture_type,
- TextureProcessMode process_mode, bool depth_compare, bool is_array) {
- const bool lod_bias_enabled =
- (process_mode != TextureProcessMode::None && process_mode != TextureProcessMode::LZ);
-
- const auto [coord_count, total_coord_count] = ValidateAndGetCoordinateElement(
- texture_type, depth_compare, is_array, lod_bias_enabled, 4, 5);
- // If enabled arrays index is always stored in the gpr8 field
- const u64 array_register = instr.gpr8.Value();
- // First coordinate index is the gpr8 or gpr8 + 1 when arrays are used
- const u64 coord_register = array_register + (is_array ? 1 : 0);
-
- std::vector<Node> coords;
- for (std::size_t i = 0; i < coord_count; ++i) {
- coords.push_back(GetRegister(coord_register + i));
- }
- // 1D.DC in OpenGL the 2nd component is ignored.
- if (depth_compare && !is_array && texture_type == TextureType::Texture1D) {
- coords.push_back(Immediate(0.0f));
- }
-
- const Node array = is_array ? GetRegister(array_register) : nullptr;
-
- Node dc{};
- if (depth_compare) {
- // Depth is always stored in the register signaled by gpr20 or in the next register if lod
- // or bias are used
- const u64 depth_register = instr.gpr20.Value() + (lod_bias_enabled ? 1 : 0);
- dc = GetRegister(depth_register);
- }
-
- return GetTextureCode(instr, texture_type, process_mode, coords, array, dc, 0);
-}
-
-Node4 ShaderIR::GetTexsCode(Instruction instr, TextureType texture_type,
- TextureProcessMode process_mode, bool depth_compare, bool is_array) {
- const bool lod_bias_enabled =
- (process_mode != TextureProcessMode::None && process_mode != TextureProcessMode::LZ);
-
- const auto [coord_count, total_coord_count] = ValidateAndGetCoordinateElement(
- texture_type, depth_compare, is_array, lod_bias_enabled, 4, 4);
- // If enabled arrays index is always stored in the gpr8 field
- const u64 array_register = instr.gpr8.Value();
- // First coordinate index is stored in gpr8 field or (gpr8 + 1) when arrays are used
- const u64 coord_register = array_register + (is_array ? 1 : 0);
- const u64 last_coord_register =
- (is_array || !(lod_bias_enabled || depth_compare) || (coord_count > 2))
- ? static_cast<u64>(instr.gpr20.Value())
- : coord_register + 1;
- const u32 bias_offset = coord_count > 2 ? 1 : 0;
-
- std::vector<Node> coords;
- for (std::size_t i = 0; i < coord_count; ++i) {
- const bool last = (i == (coord_count - 1)) && (coord_count > 1);
- coords.push_back(GetRegister(last ? last_coord_register : coord_register + i));
- }
-
- const Node array = is_array ? GetRegister(array_register) : nullptr;
-
- Node dc{};
- if (depth_compare) {
- // Depth is always stored in the register signaled by gpr20 or in the next register if lod
- // or bias are used
- const u64 depth_register = instr.gpr20.Value() + (lod_bias_enabled ? 1 : 0);
- dc = GetRegister(depth_register);
- }
-
- return GetTextureCode(instr, texture_type, process_mode, coords, array, dc, bias_offset);
-}
-
-Node4 ShaderIR::GetTld4Code(Instruction instr, TextureType texture_type, bool depth_compare,
- bool is_array) {
- const std::size_t coord_count = GetCoordCount(texture_type);
- const std::size_t total_coord_count = coord_count + (is_array ? 1 : 0);
- const std::size_t total_reg_count = total_coord_count + (depth_compare ? 1 : 0);
-
- // If enabled arrays index is always stored in the gpr8 field
- const u64 array_register = instr.gpr8.Value();
- // First coordinate index is the gpr8 or gpr8 + 1 when arrays are used
- const u64 coord_register = array_register + (is_array ? 1 : 0);
-
- std::vector<Node> coords;
- for (size_t i = 0; i < coord_count; ++i)
- coords.push_back(GetRegister(coord_register + i));
-
- const auto& sampler = GetSampler(instr.sampler, texture_type, is_array, depth_compare);
-
- Node4 values;
- for (u32 element = 0; element < values.size(); ++element) {
- auto coords_copy = coords;
- MetaTexture meta{sampler, GetRegister(array_register), {}, {}, element};
- values[element] = Operation(OperationCode::TextureGather, meta, std::move(coords_copy));
- }
-
- return values;
-}
-
-Node4 ShaderIR::GetTldsCode(Instruction instr, TextureType texture_type, bool is_array) {
- const std::size_t type_coord_count = GetCoordCount(texture_type);
- const bool lod_enabled = instr.tlds.GetTextureProcessMode() == TextureProcessMode::LL;
-
- // If enabled arrays index is always stored in the gpr8 field
- const u64 array_register = instr.gpr8.Value();
- // if is array gpr20 is used
- const u64 coord_register = is_array ? instr.gpr20.Value() : instr.gpr8.Value();
-
- const u64 last_coord_register =
- ((type_coord_count > 2) || (type_coord_count == 2 && !lod_enabled)) && !is_array
- ? static_cast<u64>(instr.gpr20.Value())
- : coord_register + 1;
-
- std::vector<Node> coords;
- for (std::size_t i = 0; i < type_coord_count; ++i) {
- const bool last = (i == (type_coord_count - 1)) && (type_coord_count > 1);
- coords.push_back(GetRegister(last ? last_coord_register : coord_register + i));
- }
-
- const Node array = is_array ? GetRegister(array_register) : nullptr;
- // When lod is used always is in gpr20
- const Node lod = lod_enabled ? GetRegister(instr.gpr20) : Immediate(0);
-
- const auto& sampler = GetSampler(instr.sampler, texture_type, is_array, false);
-
- Node4 values;
- for (u32 element = 0; element < values.size(); ++element) {
- auto coords_copy = coords;
- MetaTexture meta{sampler, array, {}, {lod}, element};
- values[element] = Operation(OperationCode::TexelFetch, meta, std::move(coords_copy));
- }
- return values;
-}
-
-std::tuple<std::size_t, std::size_t> ShaderIR::ValidateAndGetCoordinateElement(
- TextureType texture_type, bool depth_compare, bool is_array, bool lod_bias_enabled,
- std::size_t max_coords, std::size_t max_inputs) {
- const std::size_t coord_count = GetCoordCount(texture_type);
-
- std::size_t total_coord_count = coord_count + (is_array ? 1 : 0) + (depth_compare ? 1 : 0);
- const std::size_t total_reg_count = total_coord_count + (lod_bias_enabled ? 1 : 0);
- if (total_coord_count > max_coords || total_reg_count > max_inputs) {
- UNIMPLEMENTED_MSG("Unsupported Texture operation");
- total_coord_count = std::min(total_coord_count, max_coords);
- }
- // 1D.DC OpenGL is using a vec3 but 2nd component is ignored later.
- total_coord_count +=
- (depth_compare && !is_array && texture_type == TextureType::Texture1D) ? 1 : 0;
+ const auto real_address =
+ Operation(OperationCode::UAdd, NO_PRECISE, Immediate(immediate_offset), addr_register);
- return {coord_count, total_coord_count};
+ return {real_address, base_address, descriptor};
}
} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/decode/other.cpp b/src/video_core/shader/decode/other.cpp
index d750a2936..d46a8ab82 100644
--- a/src/video_core/shader/decode/other.cpp
+++ b/src/video_core/shader/decode/other.cpp
@@ -4,7 +4,9 @@
#include "common/assert.h"
#include "common/common_types.h"
+#include "common/logging/log.h"
#include "video_core/engines/shader_bytecode.h"
+#include "video_core/shader/node_helper.h"
#include "video_core/shader/shader_ir.h"
namespace VideoCommon::Shader {
@@ -13,6 +15,7 @@ using Tegra::Shader::ConditionCode;
using Tegra::Shader::Instruction;
using Tegra::Shader::OpCode;
using Tegra::Shader::Register;
+using Tegra::Shader::SystemVariable;
u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) {
const Instruction instr = {program_code[pc]};
@@ -58,20 +61,33 @@ u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) {
break;
}
case OpCode::Id::MOV_SYS: {
- switch (instr.sys20) {
- case Tegra::Shader::SystemVariable::InvocationInfo: {
- LOG_WARNING(HW_GPU, "MOV_SYS instruction with InvocationInfo is incomplete");
- SetRegister(bb, instr.gpr0, Immediate(0u));
- break;
- }
- case Tegra::Shader::SystemVariable::Ydirection: {
- // Config pack's third value is Y_NEGATE's state.
- SetRegister(bb, instr.gpr0, Operation(OperationCode::YNegate));
- break;
- }
- default:
- UNIMPLEMENTED_MSG("Unhandled system move: {}", static_cast<u32>(instr.sys20.Value()));
- }
+ const Node value = [&]() {
+ switch (instr.sys20) {
+ case SystemVariable::Ydirection:
+ return Operation(OperationCode::YNegate);
+ case SystemVariable::InvocationInfo:
+ LOG_WARNING(HW_GPU, "MOV_SYS instruction with InvocationInfo is incomplete");
+ return Immediate(0u);
+ case SystemVariable::TidX:
+ return Operation(OperationCode::LocalInvocationIdX);
+ case SystemVariable::TidY:
+ return Operation(OperationCode::LocalInvocationIdY);
+ case SystemVariable::TidZ:
+ return Operation(OperationCode::LocalInvocationIdZ);
+ case SystemVariable::CtaIdX:
+ return Operation(OperationCode::WorkGroupIdX);
+ case SystemVariable::CtaIdY:
+ return Operation(OperationCode::WorkGroupIdY);
+ case SystemVariable::CtaIdZ:
+ return Operation(OperationCode::WorkGroupIdZ);
+ default:
+ UNIMPLEMENTED_MSG("Unhandled system move: {}",
+ static_cast<u32>(instr.sys20.Value()));
+ return Immediate(0u);
+ }
+ }();
+ SetRegister(bb, instr.gpr0, value);
+
break;
}
case OpCode::Id::BRA: {
@@ -93,22 +109,20 @@ u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) {
UNIMPLEMENTED_IF_MSG(instr.bra.constant_buffer != 0,
"Constant buffer flow is not supported");
- // The SSY opcode tells the GPU where to re-converge divergent execution paths, it sets the
- // target of the jump that the SYNC instruction will make. The SSY opcode has a similar
- // structure to the BRA opcode.
+ // The SSY opcode tells the GPU where to re-converge divergent execution paths with SYNC.
const u32 target = pc + instr.bra.GetBranchTarget();
- bb.push_back(Operation(OperationCode::PushFlowStack, Immediate(target)));
+ bb.push_back(
+ Operation(OperationCode::PushFlowStack, MetaStackClass::Ssy, Immediate(target)));
break;
}
case OpCode::Id::PBK: {
UNIMPLEMENTED_IF_MSG(instr.bra.constant_buffer != 0,
"Constant buffer PBK is not supported");
- // PBK pushes to a stack the address where BRK will jump to. This shares stack with SSY but
- // using SYNC on a PBK address will kill the shader execution. We don't emulate this because
- // it's very unlikely a driver will emit such invalid shader.
+ // PBK pushes to a stack the address where BRK will jump to.
const u32 target = pc + instr.bra.GetBranchTarget();
- bb.push_back(Operation(OperationCode::PushFlowStack, Immediate(target)));
+ bb.push_back(
+ Operation(OperationCode::PushFlowStack, MetaStackClass::Pbk, Immediate(target)));
break;
}
case OpCode::Id::SYNC: {
@@ -117,7 +131,7 @@ u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) {
static_cast<u32>(cc));
// The SYNC opcode jumps to the address previously set by the SSY opcode
- bb.push_back(Operation(OperationCode::PopFlowStack));
+ bb.push_back(Operation(OperationCode::PopFlowStack, MetaStackClass::Ssy));
break;
}
case OpCode::Id::BRK: {
@@ -126,19 +140,22 @@ u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) {
static_cast<u32>(cc));
// The BRK opcode jumps to the address previously set by the PBK opcode
- bb.push_back(Operation(OperationCode::PopFlowStack));
+ bb.push_back(Operation(OperationCode::PopFlowStack, MetaStackClass::Pbk));
break;
}
case OpCode::Id::IPA: {
- const auto& attribute = instr.attribute.fmt28;
+ const bool is_physical = instr.ipa.idx && instr.gpr8.Value() != 0xff;
+
+ const auto attribute = instr.attribute.fmt28;
const Tegra::Shader::IpaMode input_mode{instr.ipa.interp_mode.Value(),
instr.ipa.sample_mode.Value()};
- const Node attr = GetInputAttribute(attribute.index, attribute.element, input_mode);
- Node value = attr;
+ Node value = is_physical ? GetPhysicalInputAttribute(instr.gpr8)
+ : GetInputAttribute(attribute.index, attribute.element);
const Tegra::Shader::Attribute::Index index = attribute.index.Value();
- if (index >= Tegra::Shader::Attribute::Index::Attribute_0 &&
- index <= Tegra::Shader::Attribute::Index::Attribute_31) {
+ const bool is_generic = index >= Tegra::Shader::Attribute::Index::Attribute_0 &&
+ index <= Tegra::Shader::Attribute::Index::Attribute_31;
+ if (is_generic || is_physical) {
// TODO(Blinkhawk): There are cases where a perspective attribute use PASS.
// In theory by setting them as perspective, OpenGL does the perspective correction.
// A way must figured to reverse the last step of it.
diff --git a/src/video_core/shader/decode/predicate_set_predicate.cpp b/src/video_core/shader/decode/predicate_set_predicate.cpp
index 83c61680e..9290d22eb 100644
--- a/src/video_core/shader/decode/predicate_set_predicate.cpp
+++ b/src/video_core/shader/decode/predicate_set_predicate.cpp
@@ -5,6 +5,7 @@
#include "common/assert.h"
#include "common/common_types.h"
#include "video_core/engines/shader_bytecode.h"
+#include "video_core/shader/node_helper.h"
#include "video_core/shader/shader_ir.h"
namespace VideoCommon::Shader {
@@ -64,4 +65,4 @@ u32 ShaderIR::DecodePredicateSetPredicate(NodeBlock& bb, u32 pc) {
return pc;
}
-} // namespace VideoCommon::Shader \ No newline at end of file
+} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/decode/predicate_set_register.cpp b/src/video_core/shader/decode/predicate_set_register.cpp
index d0495995d..febbfeb50 100644
--- a/src/video_core/shader/decode/predicate_set_register.cpp
+++ b/src/video_core/shader/decode/predicate_set_register.cpp
@@ -5,6 +5,7 @@
#include "common/assert.h"
#include "common/common_types.h"
#include "video_core/engines/shader_bytecode.h"
+#include "video_core/shader/node_helper.h"
#include "video_core/shader/shader_ir.h"
namespace VideoCommon::Shader {
@@ -43,4 +44,4 @@ u32 ShaderIR::DecodePredicateSetRegister(NodeBlock& bb, u32 pc) {
return pc;
}
-} // namespace VideoCommon::Shader \ No newline at end of file
+} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/decode/register_set_predicate.cpp b/src/video_core/shader/decode/register_set_predicate.cpp
index f070e8912..e6c9d287e 100644
--- a/src/video_core/shader/decode/register_set_predicate.cpp
+++ b/src/video_core/shader/decode/register_set_predicate.cpp
@@ -5,6 +5,7 @@
#include "common/assert.h"
#include "common/common_types.h"
#include "video_core/engines/shader_bytecode.h"
+#include "video_core/shader/node_helper.h"
#include "video_core/shader/shader_ir.h"
namespace VideoCommon::Shader {
@@ -48,4 +49,4 @@ u32 ShaderIR::DecodeRegisterSetPredicate(NodeBlock& bb, u32 pc) {
return pc;
}
-} // namespace VideoCommon::Shader \ No newline at end of file
+} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/decode/shift.cpp b/src/video_core/shader/decode/shift.cpp
index 951e85f44..2ac16eeb0 100644
--- a/src/video_core/shader/decode/shift.cpp
+++ b/src/video_core/shader/decode/shift.cpp
@@ -5,6 +5,7 @@
#include "common/assert.h"
#include "common/common_types.h"
#include "video_core/engines/shader_bytecode.h"
+#include "video_core/shader/node_helper.h"
#include "video_core/shader/shader_ir.h"
namespace VideoCommon::Shader {
@@ -52,4 +53,4 @@ u32 ShaderIR::DecodeShift(NodeBlock& bb, u32 pc) {
return pc;
}
-} // namespace VideoCommon::Shader \ No newline at end of file
+} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/decode/texture.cpp b/src/video_core/shader/decode/texture.cpp
new file mode 100644
index 000000000..4a356dbd4
--- /dev/null
+++ b/src/video_core/shader/decode/texture.cpp
@@ -0,0 +1,672 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <vector>
+#include <fmt/format.h>
+
+#include "common/assert.h"
+#include "common/bit_field.h"
+#include "common/common_types.h"
+#include "common/logging/log.h"
+#include "video_core/engines/shader_bytecode.h"
+#include "video_core/shader/node_helper.h"
+#include "video_core/shader/shader_ir.h"
+
+namespace VideoCommon::Shader {
+
+using Tegra::Shader::Instruction;
+using Tegra::Shader::OpCode;
+using Tegra::Shader::Register;
+using Tegra::Shader::TextureMiscMode;
+using Tegra::Shader::TextureProcessMode;
+using Tegra::Shader::TextureType;
+
+static std::size_t GetCoordCount(TextureType texture_type) {
+ switch (texture_type) {
+ case TextureType::Texture1D:
+ return 1;
+ case TextureType::Texture2D:
+ return 2;
+ case TextureType::Texture3D:
+ case TextureType::TextureCube:
+ return 3;
+ default:
+ UNIMPLEMENTED_MSG("Unhandled texture type: {}", static_cast<u32>(texture_type));
+ return 0;
+ }
+}
+
+u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
+ const Instruction instr = {program_code[pc]};
+ const auto opcode = OpCode::Decode(instr);
+ bool is_bindless = false;
+ switch (opcode->get().GetId()) {
+ case OpCode::Id::TEX: {
+ if (instr.tex.UsesMiscMode(TextureMiscMode::NODEP)) {
+ LOG_WARNING(HW_GPU, "TEX.NODEP implementation is incomplete");
+ }
+
+ const TextureType texture_type{instr.tex.texture_type};
+ const bool is_array = instr.tex.array != 0;
+ const bool is_aoffi = instr.tex.UsesMiscMode(TextureMiscMode::AOFFI);
+ const bool depth_compare = instr.tex.UsesMiscMode(TextureMiscMode::DC);
+ const auto process_mode = instr.tex.GetTextureProcessMode();
+ WriteTexInstructionFloat(
+ bb, instr,
+ GetTexCode(instr, texture_type, process_mode, depth_compare, is_array, is_aoffi, {}));
+ break;
+ }
+ case OpCode::Id::TEX_B: {
+ UNIMPLEMENTED_IF_MSG(instr.tex.UsesMiscMode(TextureMiscMode::AOFFI),
+ "AOFFI is not implemented");
+
+ if (instr.tex.UsesMiscMode(TextureMiscMode::NODEP)) {
+ LOG_WARNING(HW_GPU, "TEX.NODEP implementation is incomplete");
+ }
+
+ const TextureType texture_type{instr.tex_b.texture_type};
+ const bool is_array = instr.tex_b.array != 0;
+ const bool is_aoffi = instr.tex.UsesMiscMode(TextureMiscMode::AOFFI);
+ const bool depth_compare = instr.tex_b.UsesMiscMode(TextureMiscMode::DC);
+ const auto process_mode = instr.tex_b.GetTextureProcessMode();
+ WriteTexInstructionFloat(bb, instr,
+ GetTexCode(instr, texture_type, process_mode, depth_compare,
+ is_array, is_aoffi, {instr.gpr20}));
+ break;
+ }
+ case OpCode::Id::TEXS: {
+ const TextureType texture_type{instr.texs.GetTextureType()};
+ const bool is_array{instr.texs.IsArrayTexture()};
+ const bool depth_compare = instr.texs.UsesMiscMode(TextureMiscMode::DC);
+ const auto process_mode = instr.texs.GetTextureProcessMode();
+
+ if (instr.texs.UsesMiscMode(TextureMiscMode::NODEP)) {
+ LOG_WARNING(HW_GPU, "TEXS.NODEP implementation is incomplete");
+ }
+
+ const Node4 components =
+ GetTexsCode(instr, texture_type, process_mode, depth_compare, is_array);
+
+ if (instr.texs.fp32_flag) {
+ WriteTexsInstructionFloat(bb, instr, components);
+ } else {
+ WriteTexsInstructionHalfFloat(bb, instr, components);
+ }
+ break;
+ }
+ case OpCode::Id::TLD4: {
+ ASSERT(instr.tld4.array == 0);
+ UNIMPLEMENTED_IF_MSG(instr.tld4.UsesMiscMode(TextureMiscMode::NDV),
+ "NDV is not implemented");
+ UNIMPLEMENTED_IF_MSG(instr.tld4.UsesMiscMode(TextureMiscMode::PTP),
+ "PTP is not implemented");
+
+ if (instr.tld4.UsesMiscMode(TextureMiscMode::NODEP)) {
+ LOG_WARNING(HW_GPU, "TLD4.NODEP implementation is incomplete");
+ }
+
+ const auto texture_type = instr.tld4.texture_type.Value();
+ const bool depth_compare = instr.tld4.UsesMiscMode(TextureMiscMode::DC);
+ const bool is_array = instr.tld4.array != 0;
+ const bool is_aoffi = instr.tld4.UsesMiscMode(TextureMiscMode::AOFFI);
+ WriteTexInstructionFloat(
+ bb, instr, GetTld4Code(instr, texture_type, depth_compare, is_array, is_aoffi));
+ break;
+ }
+ case OpCode::Id::TLD4S: {
+ UNIMPLEMENTED_IF_MSG(instr.tld4s.UsesMiscMode(TextureMiscMode::AOFFI),
+ "AOFFI is not implemented");
+ if (instr.tld4s.UsesMiscMode(TextureMiscMode::NODEP)) {
+ LOG_WARNING(HW_GPU, "TLD4S.NODEP implementation is incomplete");
+ }
+
+ const bool depth_compare = instr.tld4s.UsesMiscMode(TextureMiscMode::DC);
+ const Node op_a = GetRegister(instr.gpr8);
+ const Node op_b = GetRegister(instr.gpr20);
+
+ // TODO(Subv): Figure out how the sampler type is encoded in the TLD4S instruction.
+ std::vector<Node> coords;
+ if (depth_compare) {
+ // Note: TLD4S coordinate encoding works just like TEXS's
+ const Node op_y = GetRegister(instr.gpr8.Value() + 1);
+ coords.push_back(op_a);
+ coords.push_back(op_y);
+ coords.push_back(op_b);
+ } else {
+ coords.push_back(op_a);
+ coords.push_back(op_b);
+ }
+ const Node component = Immediate(static_cast<u32>(instr.tld4s.component));
+
+ const auto& sampler =
+ GetSampler(instr.sampler, TextureType::Texture2D, false, depth_compare);
+
+ Node4 values;
+ for (u32 element = 0; element < values.size(); ++element) {
+ auto coords_copy = coords;
+ MetaTexture meta{sampler, {}, {}, {}, {}, {}, component, element};
+ values[element] = Operation(OperationCode::TextureGather, meta, std::move(coords_copy));
+ }
+
+ WriteTexsInstructionFloat(bb, instr, values);
+ break;
+ }
+ case OpCode::Id::TXQ_B:
+ is_bindless = true;
+ [[fallthrough]];
+ case OpCode::Id::TXQ: {
+ if (instr.txq.UsesMiscMode(TextureMiscMode::NODEP)) {
+ LOG_WARNING(HW_GPU, "TXQ.NODEP implementation is incomplete");
+ }
+
+ // TODO: The new commits on the texture refactor, change the way samplers work.
+ // Sadly, not all texture instructions specify the type of texture their sampler
+ // uses. This must be fixed at a later instance.
+ const auto& sampler =
+ is_bindless
+ ? GetBindlessSampler(instr.gpr8, Tegra::Shader::TextureType::Texture2D, false,
+ false)
+ : GetSampler(instr.sampler, Tegra::Shader::TextureType::Texture2D, false, false);
+
+ u32 indexer = 0;
+ switch (instr.txq.query_type) {
+ case Tegra::Shader::TextureQueryType::Dimension: {
+ for (u32 element = 0; element < 4; ++element) {
+ if (!instr.txq.IsComponentEnabled(element)) {
+ continue;
+ }
+ MetaTexture meta{sampler, {}, {}, {}, {}, {}, {}, element};
+ const Node value =
+ Operation(OperationCode::TextureQueryDimensions, meta,
+ GetRegister(instr.gpr8.Value() + (is_bindless ? 1 : 0)));
+ SetTemporal(bb, indexer++, value);
+ }
+ for (u32 i = 0; i < indexer; ++i) {
+ SetRegister(bb, instr.gpr0.Value() + i, GetTemporal(i));
+ }
+ break;
+ }
+ default:
+ UNIMPLEMENTED_MSG("Unhandled texture query type: {}",
+ static_cast<u32>(instr.txq.query_type.Value()));
+ }
+ break;
+ }
+ case OpCode::Id::TMML_B:
+ is_bindless = true;
+ [[fallthrough]];
+ case OpCode::Id::TMML: {
+ UNIMPLEMENTED_IF_MSG(instr.tmml.UsesMiscMode(Tegra::Shader::TextureMiscMode::NDV),
+ "NDV is not implemented");
+
+ if (instr.tmml.UsesMiscMode(TextureMiscMode::NODEP)) {
+ LOG_WARNING(HW_GPU, "TMML.NODEP implementation is incomplete");
+ }
+
+ auto texture_type = instr.tmml.texture_type.Value();
+ const bool is_array = instr.tmml.array != 0;
+ const auto& sampler = is_bindless
+ ? GetBindlessSampler(instr.gpr20, texture_type, is_array, false)
+ : GetSampler(instr.sampler, texture_type, is_array, false);
+
+ 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<u32>(texture_type));
+
+ // 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));
+ texture_type = TextureType::Texture2D;
+ }
+ u32 indexer = 0;
+ for (u32 element = 0; element < 2; ++element) {
+ if (!instr.tmml.IsComponentEnabled(element)) {
+ continue;
+ }
+ auto params = coords;
+ MetaTexture meta{sampler, {}, {}, {}, {}, {}, {}, element};
+ const Node value = Operation(OperationCode::TextureQueryLod, meta, std::move(params));
+ SetTemporal(bb, indexer++, value);
+ }
+ for (u32 i = 0; i < indexer; ++i) {
+ SetRegister(bb, instr.gpr0.Value() + i, GetTemporal(i));
+ }
+ break;
+ }
+ case OpCode::Id::TLDS: {
+ const Tegra::Shader::TextureType texture_type{instr.tlds.GetTextureType()};
+ const bool is_array{instr.tlds.IsArrayTexture()};
+
+ UNIMPLEMENTED_IF_MSG(instr.tlds.UsesMiscMode(TextureMiscMode::AOFFI),
+ "AOFFI is not implemented");
+ UNIMPLEMENTED_IF_MSG(instr.tlds.UsesMiscMode(TextureMiscMode::MZ), "MZ is not implemented");
+
+ if (instr.tlds.UsesMiscMode(TextureMiscMode::NODEP)) {
+ LOG_WARNING(HW_GPU, "TLDS.NODEP implementation is incomplete");
+ }
+
+ WriteTexsInstructionFloat(bb, instr, GetTldsCode(instr, texture_type, is_array));
+ break;
+ }
+ default:
+ UNIMPLEMENTED_MSG("Unhandled memory instruction: {}", opcode->get().GetName());
+ }
+
+ return pc;
+}
+
+const Sampler& ShaderIR::GetSampler(const Tegra::Shader::Sampler& sampler, TextureType type,
+ bool is_array, bool is_shadow) {
+ const auto offset = static_cast<std::size_t>(sampler.index.Value());
+
+ // If this sampler has already been used, return the existing mapping.
+ const auto itr =
+ std::find_if(used_samplers.begin(), used_samplers.end(),
+ [&](const Sampler& entry) { return entry.GetOffset() == offset; });
+ if (itr != used_samplers.end()) {
+ ASSERT(itr->GetType() == type && itr->IsArray() == is_array &&
+ itr->IsShadow() == is_shadow);
+ return *itr;
+ }
+
+ // Otherwise create a new mapping for this sampler
+ const std::size_t next_index = used_samplers.size();
+ const Sampler entry{offset, next_index, type, is_array, is_shadow};
+ return *used_samplers.emplace(entry).first;
+}
+
+const Sampler& ShaderIR::GetBindlessSampler(const Tegra::Shader::Register& reg, TextureType type,
+ bool is_array, bool is_shadow) {
+ const Node sampler_register = GetRegister(reg);
+ const Node base_sampler =
+ TrackCbuf(sampler_register, global_code, static_cast<s64>(global_code.size()));
+ const auto cbuf = std::get_if<CbufNode>(&*base_sampler);
+ const auto cbuf_offset_imm = std::get_if<ImmediateNode>(&*cbuf->GetOffset());
+ ASSERT(cbuf_offset_imm != nullptr);
+ const auto cbuf_offset = cbuf_offset_imm->GetValue();
+ const auto cbuf_index = cbuf->GetIndex();
+ const auto cbuf_key = (static_cast<u64>(cbuf_index) << 32) | static_cast<u64>(cbuf_offset);
+
+ // If this sampler has already been used, return the existing mapping.
+ const auto itr =
+ std::find_if(used_samplers.begin(), used_samplers.end(),
+ [&](const Sampler& entry) { return entry.GetOffset() == cbuf_key; });
+ if (itr != used_samplers.end()) {
+ ASSERT(itr->GetType() == type && itr->IsArray() == is_array &&
+ itr->IsShadow() == is_shadow);
+ return *itr;
+ }
+
+ // Otherwise create a new mapping for this sampler
+ const std::size_t next_index = used_samplers.size();
+ const Sampler entry{cbuf_index, cbuf_offset, next_index, type, is_array, is_shadow};
+ return *used_samplers.emplace(entry).first;
+}
+
+void ShaderIR::WriteTexInstructionFloat(NodeBlock& bb, Instruction instr, const Node4& components) {
+ u32 dest_elem = 0;
+ for (u32 elem = 0; elem < 4; ++elem) {
+ if (!instr.tex.IsComponentEnabled(elem)) {
+ // Skip disabled components
+ continue;
+ }
+ SetTemporal(bb, dest_elem++, components[elem]);
+ }
+ // After writing values in temporals, move them to the real registers
+ for (u32 i = 0; i < dest_elem; ++i) {
+ SetRegister(bb, instr.gpr0.Value() + i, GetTemporal(i));
+ }
+}
+
+void ShaderIR::WriteTexsInstructionFloat(NodeBlock& bb, Instruction instr,
+ const Node4& components) {
+ // TEXS has two destination registers and a swizzle. The first two elements in the swizzle
+ // go into gpr0+0 and gpr0+1, and the rest goes into gpr28+0 and gpr28+1
+
+ u32 dest_elem = 0;
+ for (u32 component = 0; component < 4; ++component) {
+ if (!instr.texs.IsComponentEnabled(component))
+ continue;
+ SetTemporal(bb, dest_elem++, components[component]);
+ }
+
+ for (u32 i = 0; i < dest_elem; ++i) {
+ if (i < 2) {
+ // Write the first two swizzle components to gpr0 and gpr0+1
+ SetRegister(bb, instr.gpr0.Value() + i % 2, GetTemporal(i));
+ } else {
+ ASSERT(instr.texs.HasTwoDestinations());
+ // Write the rest of the swizzle components to gpr28 and gpr28+1
+ SetRegister(bb, instr.gpr28.Value() + i % 2, GetTemporal(i));
+ }
+ }
+}
+
+void ShaderIR::WriteTexsInstructionHalfFloat(NodeBlock& bb, Instruction instr,
+ const Node4& components) {
+ // TEXS.F16 destionation registers are packed in two registers in pairs (just like any half
+ // float instruction).
+
+ Node4 values;
+ u32 dest_elem = 0;
+ for (u32 component = 0; component < 4; ++component) {
+ if (!instr.texs.IsComponentEnabled(component))
+ continue;
+ values[dest_elem++] = components[component];
+ }
+ if (dest_elem == 0)
+ return;
+
+ std::generate(values.begin() + dest_elem, values.end(), [&]() { return Immediate(0); });
+
+ const Node first_value = Operation(OperationCode::HPack2, values[0], values[1]);
+ if (dest_elem <= 2) {
+ SetRegister(bb, instr.gpr0, first_value);
+ return;
+ }
+
+ SetTemporal(bb, 0, first_value);
+ SetTemporal(bb, 1, Operation(OperationCode::HPack2, values[2], values[3]));
+
+ SetRegister(bb, instr.gpr0, GetTemporal(0));
+ SetRegister(bb, instr.gpr28, GetTemporal(1));
+}
+
+Node4 ShaderIR::GetTextureCode(Instruction instr, TextureType texture_type,
+ TextureProcessMode process_mode, std::vector<Node> coords,
+ Node array, Node depth_compare, u32 bias_offset,
+ std::vector<Node> aoffi,
+ std::optional<Tegra::Shader::Register> bindless_reg) {
+ const auto is_array = static_cast<bool>(array);
+ const auto is_shadow = static_cast<bool>(depth_compare);
+ const bool is_bindless = bindless_reg.has_value();
+
+ UNIMPLEMENTED_IF_MSG((texture_type == TextureType::Texture3D && (is_array || is_shadow)) ||
+ (texture_type == TextureType::TextureCube && is_array && is_shadow),
+ "This method is not supported.");
+
+ const auto& sampler = is_bindless
+ ? GetBindlessSampler(*bindless_reg, texture_type, is_array, is_shadow)
+ : GetSampler(instr.sampler, texture_type, is_array, is_shadow);
+
+ const bool lod_needed = process_mode == TextureProcessMode::LZ ||
+ process_mode == TextureProcessMode::LL ||
+ process_mode == TextureProcessMode::LLA;
+
+ // LOD selection (either via bias or explicit textureLod) not
+ // supported in GL for sampler2DArrayShadow and
+ // samplerCubeArrayShadow.
+ const bool gl_lod_supported =
+ !((texture_type == Tegra::Shader::TextureType::Texture2D && is_array && is_shadow) ||
+ (texture_type == Tegra::Shader::TextureType::TextureCube && is_array && is_shadow));
+
+ const OperationCode read_method =
+ (lod_needed && gl_lod_supported) ? OperationCode::TextureLod : OperationCode::Texture;
+
+ UNIMPLEMENTED_IF(process_mode != TextureProcessMode::None && !gl_lod_supported);
+
+ Node bias = {};
+ Node lod = {};
+ if (process_mode != TextureProcessMode::None && gl_lod_supported) {
+ switch (process_mode) {
+ case TextureProcessMode::LZ:
+ lod = Immediate(0.0f);
+ break;
+ case TextureProcessMode::LB:
+ // If present, lod or bias are always stored in the register
+ // indexed by the gpr20 field with an offset depending on the
+ // usage of the other registers
+ bias = GetRegister(instr.gpr20.Value() + bias_offset);
+ break;
+ case TextureProcessMode::LL:
+ lod = GetRegister(instr.gpr20.Value() + bias_offset);
+ break;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented process mode={}", static_cast<u32>(process_mode));
+ break;
+ }
+ }
+
+ Node4 values;
+ for (u32 element = 0; element < values.size(); ++element) {
+ auto copy_coords = coords;
+ MetaTexture meta{sampler, array, depth_compare, aoffi, bias, lod, {}, element};
+ values[element] = Operation(read_method, meta, std::move(copy_coords));
+ }
+
+ return values;
+}
+
+Node4 ShaderIR::GetTexCode(Instruction instr, TextureType texture_type,
+ TextureProcessMode process_mode, bool depth_compare, bool is_array,
+ bool is_aoffi, std::optional<Tegra::Shader::Register> bindless_reg) {
+ const bool lod_bias_enabled{
+ (process_mode != TextureProcessMode::None && process_mode != TextureProcessMode::LZ)};
+
+ const bool is_bindless = bindless_reg.has_value();
+
+ u64 parameter_register = instr.gpr20.Value();
+ if (is_bindless) {
+ ++parameter_register;
+ }
+
+ const u32 bias_lod_offset = (is_bindless ? 1 : 0);
+ if (lod_bias_enabled) {
+ ++parameter_register;
+ }
+
+ const auto [coord_count, total_coord_count] = ValidateAndGetCoordinateElement(
+ texture_type, depth_compare, is_array, lod_bias_enabled, 4, 5);
+ // If enabled arrays index is always stored in the gpr8 field
+ const u64 array_register = instr.gpr8.Value();
+ // First coordinate index is the gpr8 or gpr8 + 1 when arrays are used
+ const u64 coord_register = array_register + (is_array ? 1 : 0);
+
+ std::vector<Node> coords;
+ for (std::size_t i = 0; i < coord_count; ++i) {
+ coords.push_back(GetRegister(coord_register + i));
+ }
+ // 1D.DC in OpenGL the 2nd component is ignored.
+ if (depth_compare && !is_array && texture_type == TextureType::Texture1D) {
+ coords.push_back(Immediate(0.0f));
+ }
+
+ const Node array = is_array ? GetRegister(array_register) : nullptr;
+
+ std::vector<Node> aoffi;
+ if (is_aoffi) {
+ aoffi = GetAoffiCoordinates(GetRegister(parameter_register++), coord_count, false);
+ }
+
+ Node dc{};
+ if (depth_compare) {
+ // Depth is always stored in the register signaled by gpr20 or in the next register if lod
+ // or bias are used
+ dc = GetRegister(parameter_register++);
+ }
+
+ return GetTextureCode(instr, texture_type, process_mode, coords, array, dc, bias_lod_offset,
+ aoffi, bindless_reg);
+}
+
+Node4 ShaderIR::GetTexsCode(Instruction instr, TextureType texture_type,
+ TextureProcessMode process_mode, bool depth_compare, bool is_array) {
+ const bool lod_bias_enabled =
+ (process_mode != TextureProcessMode::None && process_mode != TextureProcessMode::LZ);
+
+ const auto [coord_count, total_coord_count] = ValidateAndGetCoordinateElement(
+ texture_type, depth_compare, is_array, lod_bias_enabled, 4, 4);
+ // If enabled arrays index is always stored in the gpr8 field
+ const u64 array_register = instr.gpr8.Value();
+ // First coordinate index is stored in gpr8 field or (gpr8 + 1) when arrays are used
+ const u64 coord_register = array_register + (is_array ? 1 : 0);
+ const u64 last_coord_register =
+ (is_array || !(lod_bias_enabled || depth_compare) || (coord_count > 2))
+ ? static_cast<u64>(instr.gpr20.Value())
+ : coord_register + 1;
+ const u32 bias_offset = coord_count > 2 ? 1 : 0;
+
+ std::vector<Node> coords;
+ for (std::size_t i = 0; i < coord_count; ++i) {
+ const bool last = (i == (coord_count - 1)) && (coord_count > 1);
+ coords.push_back(GetRegister(last ? last_coord_register : coord_register + i));
+ }
+
+ const Node array = is_array ? GetRegister(array_register) : nullptr;
+
+ Node dc{};
+ if (depth_compare) {
+ // Depth is always stored in the register signaled by gpr20 or in the next register if lod
+ // or bias are used
+ const u64 depth_register = instr.gpr20.Value() + (lod_bias_enabled ? 1 : 0);
+ dc = GetRegister(depth_register);
+ }
+
+ return GetTextureCode(instr, texture_type, process_mode, coords, array, dc, bias_offset, {},
+ {});
+}
+
+Node4 ShaderIR::GetTld4Code(Instruction instr, TextureType texture_type, bool depth_compare,
+ bool is_array, bool is_aoffi) {
+ const std::size_t coord_count = GetCoordCount(texture_type);
+
+ // If enabled arrays index is always stored in the gpr8 field
+ const u64 array_register = instr.gpr8.Value();
+ // First coordinate index is the gpr8 or gpr8 + 1 when arrays are used
+ const u64 coord_register = array_register + (is_array ? 1 : 0);
+
+ std::vector<Node> coords;
+ for (std::size_t i = 0; i < coord_count; ++i) {
+ coords.push_back(GetRegister(coord_register + i));
+ }
+
+ u64 parameter_register = instr.gpr20.Value();
+ std::vector<Node> aoffi;
+ if (is_aoffi) {
+ aoffi = GetAoffiCoordinates(GetRegister(parameter_register++), coord_count, true);
+ }
+
+ Node dc{};
+ if (depth_compare) {
+ dc = GetRegister(parameter_register++);
+ }
+
+ const auto& sampler = GetSampler(instr.sampler, texture_type, is_array, depth_compare);
+
+ Node4 values;
+ for (u32 element = 0; element < values.size(); ++element) {
+ auto coords_copy = coords;
+ MetaTexture meta{sampler, GetRegister(array_register), dc, aoffi, {}, {}, {}, element};
+ values[element] = Operation(OperationCode::TextureGather, meta, std::move(coords_copy));
+ }
+
+ return values;
+}
+
+Node4 ShaderIR::GetTldsCode(Instruction instr, TextureType texture_type, bool is_array) {
+ const std::size_t type_coord_count = GetCoordCount(texture_type);
+ const bool lod_enabled = instr.tlds.GetTextureProcessMode() == TextureProcessMode::LL;
+
+ // If enabled arrays index is always stored in the gpr8 field
+ const u64 array_register = instr.gpr8.Value();
+ // if is array gpr20 is used
+ const u64 coord_register = is_array ? instr.gpr20.Value() : instr.gpr8.Value();
+
+ const u64 last_coord_register =
+ ((type_coord_count > 2) || (type_coord_count == 2 && !lod_enabled)) && !is_array
+ ? static_cast<u64>(instr.gpr20.Value())
+ : coord_register + 1;
+
+ std::vector<Node> coords;
+ for (std::size_t i = 0; i < type_coord_count; ++i) {
+ const bool last = (i == (type_coord_count - 1)) && (type_coord_count > 1);
+ coords.push_back(GetRegister(last ? last_coord_register : coord_register + i));
+ }
+
+ const Node array = is_array ? GetRegister(array_register) : nullptr;
+ // When lod is used always is in gpr20
+ const Node lod = lod_enabled ? GetRegister(instr.gpr20) : Immediate(0);
+
+ const auto& sampler = GetSampler(instr.sampler, texture_type, is_array, false);
+
+ Node4 values;
+ for (u32 element = 0; element < values.size(); ++element) {
+ auto coords_copy = coords;
+ MetaTexture meta{sampler, array, {}, {}, {}, lod, {}, element};
+ values[element] = Operation(OperationCode::TexelFetch, meta, std::move(coords_copy));
+ }
+ return values;
+}
+
+std::tuple<std::size_t, std::size_t> ShaderIR::ValidateAndGetCoordinateElement(
+ TextureType texture_type, bool depth_compare, bool is_array, bool lod_bias_enabled,
+ std::size_t max_coords, std::size_t max_inputs) {
+ const std::size_t coord_count = GetCoordCount(texture_type);
+
+ std::size_t total_coord_count = coord_count + (is_array ? 1 : 0) + (depth_compare ? 1 : 0);
+ const std::size_t total_reg_count = total_coord_count + (lod_bias_enabled ? 1 : 0);
+ if (total_coord_count > max_coords || total_reg_count > max_inputs) {
+ UNIMPLEMENTED_MSG("Unsupported Texture operation");
+ total_coord_count = std::min(total_coord_count, max_coords);
+ }
+ // 1D.DC OpenGL is using a vec3 but 2nd component is ignored later.
+ total_coord_count +=
+ (depth_compare && !is_array && texture_type == TextureType::Texture1D) ? 1 : 0;
+
+ return {coord_count, total_coord_count};
+}
+
+std::vector<Node> ShaderIR::GetAoffiCoordinates(Node aoffi_reg, std::size_t coord_count,
+ bool is_tld4) {
+ const auto [coord_offsets, size, wrap_value,
+ diff_value] = [is_tld4]() -> std::tuple<std::array<u32, 3>, u32, s32, s32> {
+ if (is_tld4) {
+ return {{0, 8, 16}, 6, 32, 64};
+ } else {
+ return {{0, 4, 8}, 4, 8, 16};
+ }
+ }();
+ const u32 mask = (1U << size) - 1;
+
+ std::vector<Node> aoffi;
+ aoffi.reserve(coord_count);
+
+ const auto aoffi_immediate{
+ TrackImmediate(aoffi_reg, global_code, static_cast<s64>(global_code.size()))};
+ if (!aoffi_immediate) {
+ // Variable access, not supported on AMD.
+ LOG_WARNING(HW_GPU,
+ "AOFFI constant folding failed, some hardware might have graphical issues");
+ for (std::size_t coord = 0; coord < coord_count; ++coord) {
+ const Node value = BitfieldExtract(aoffi_reg, coord_offsets.at(coord), size);
+ const Node condition =
+ Operation(OperationCode::LogicalIGreaterEqual, value, Immediate(wrap_value));
+ const Node negative = Operation(OperationCode::IAdd, value, Immediate(-diff_value));
+ aoffi.push_back(Operation(OperationCode::Select, condition, negative, value));
+ }
+ return aoffi;
+ }
+
+ for (std::size_t coord = 0; coord < coord_count; ++coord) {
+ s32 value = (*aoffi_immediate >> coord_offsets.at(coord)) & mask;
+ if (value >= wrap_value) {
+ value -= diff_value;
+ }
+ aoffi.push_back(Immediate(value));
+ }
+ return aoffi;
+}
+
+} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/decode/video.cpp b/src/video_core/shader/decode/video.cpp
index 956c01d9b..97fc6f9b1 100644
--- a/src/video_core/shader/decode/video.cpp
+++ b/src/video_core/shader/decode/video.cpp
@@ -5,6 +5,7 @@
#include "common/assert.h"
#include "common/common_types.h"
#include "video_core/engines/shader_bytecode.h"
+#include "video_core/shader/node_helper.h"
#include "video_core/shader/shader_ir.h"
namespace VideoCommon::Shader {
@@ -108,4 +109,4 @@ Node ShaderIR::GetVideoOperand(Node op, bool is_chunk, bool is_signed,
}
}
-} // namespace VideoCommon::Shader \ No newline at end of file
+} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/decode/xmad.cpp b/src/video_core/shader/decode/xmad.cpp
index c34843307..93dee77d1 100644
--- a/src/video_core/shader/decode/xmad.cpp
+++ b/src/video_core/shader/decode/xmad.cpp
@@ -5,6 +5,7 @@
#include "common/assert.h"
#include "common/common_types.h"
#include "video_core/engines/shader_bytecode.h"
+#include "video_core/shader/node_helper.h"
#include "video_core/shader/shader_ir.h"
namespace VideoCommon::Shader {
@@ -29,39 +30,56 @@ u32 ShaderIR::DecodeXmad(NodeBlock& bb, u32 pc) {
const bool is_signed_b = instr.xmad.sign_b == 1;
const bool is_signed_c = is_signed_a;
- auto [is_merge, op_b, op_c] = [&]() -> std::tuple<bool, Node, Node> {
+ auto [is_merge, is_psl, is_high_b, mode, op_b,
+ op_c] = [&]() -> std::tuple<bool, bool, bool, Tegra::Shader::XmadMode, Node, Node> {
switch (opcode->get().GetId()) {
case OpCode::Id::XMAD_CR:
return {instr.xmad.merge_56,
+ instr.xmad.product_shift_left_second,
+ instr.xmad.high_b,
+ instr.xmad.mode_cbf,
GetConstBuffer(instr.cbuf34.index, instr.cbuf34.GetOffset()),
GetRegister(instr.gpr39)};
case OpCode::Id::XMAD_RR:
- return {instr.xmad.merge_37, GetRegister(instr.gpr20), GetRegister(instr.gpr39)};
+ return {instr.xmad.merge_37, instr.xmad.product_shift_left, instr.xmad.high_b_rr,
+ instr.xmad.mode, GetRegister(instr.gpr20), GetRegister(instr.gpr39)};
case OpCode::Id::XMAD_RC:
- return {false, GetRegister(instr.gpr39),
+ return {false,
+ false,
+ instr.xmad.high_b,
+ instr.xmad.mode_cbf,
+ GetRegister(instr.gpr39),
GetConstBuffer(instr.cbuf34.index, instr.cbuf34.GetOffset())};
case OpCode::Id::XMAD_IMM:
- return {instr.xmad.merge_37, Immediate(static_cast<u32>(instr.xmad.imm20_16)),
+ return {instr.xmad.merge_37,
+ instr.xmad.product_shift_left,
+ false,
+ instr.xmad.mode,
+ Immediate(static_cast<u32>(instr.xmad.imm20_16)),
GetRegister(instr.gpr39)};
+ default:
+ UNIMPLEMENTED_MSG("Unhandled XMAD instruction: {}", opcode->get().GetName());
+ return {false, false, false, Tegra::Shader::XmadMode::None, Immediate(0), Immediate(0)};
}
- UNIMPLEMENTED_MSG("Unhandled XMAD instruction: {}", opcode->get().GetName());
- return {false, Immediate(0), Immediate(0)};
}();
op_a = BitfieldExtract(op_a, instr.xmad.high_a ? 16 : 0, 16);
const Node original_b = op_b;
- op_b = BitfieldExtract(op_b, instr.xmad.high_b ? 16 : 0, 16);
+ op_b = BitfieldExtract(op_b, is_high_b ? 16 : 0, 16);
// TODO(Rodrigo): Use an appropiate sign for this operation
Node product = Operation(OperationCode::IMul, NO_PRECISE, op_a, op_b);
- if (instr.xmad.product_shift_left) {
+ if (is_psl) {
product = Operation(OperationCode::ILogicalShiftLeft, NO_PRECISE, product, Immediate(16));
}
+ SetTemporal(bb, 0, product);
+ product = GetTemporal(0);
const Node original_c = op_c;
+ const Tegra::Shader::XmadMode set_mode = mode; // Workaround to clang compile error
op_c = [&]() {
- switch (instr.xmad.mode) {
+ switch (set_mode) {
case Tegra::Shader::XmadMode::None:
return original_c;
case Tegra::Shader::XmadMode::CLo:
@@ -80,8 +98,13 @@ u32 ShaderIR::DecodeXmad(NodeBlock& bb, u32 pc) {
}
}();
+ SetTemporal(bb, 1, op_c);
+ op_c = GetTemporal(1);
+
// TODO(Rodrigo): Use an appropiate sign for this operation
Node sum = Operation(OperationCode::IAdd, product, op_c);
+ SetTemporal(bb, 2, sum);
+ sum = GetTemporal(2);
if (is_merge) {
const Node a = BitfieldExtract(sum, 0, 16);
const Node b =
@@ -95,4 +118,4 @@ u32 ShaderIR::DecodeXmad(NodeBlock& bb, u32 pc) {
return pc;
}
-} // namespace VideoCommon::Shader \ No newline at end of file
+} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/node.h b/src/video_core/shader/node.h
new file mode 100644
index 000000000..3cfb911bb
--- /dev/null
+++ b/src/video_core/shader/node.h
@@ -0,0 +1,519 @@
+// 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 <string>
+#include <tuple>
+#include <utility>
+#include <variant>
+#include <vector>
+
+#include "common/common_types.h"
+#include "video_core/engines/shader_bytecode.h"
+
+namespace VideoCommon::Shader {
+
+enum class OperationCode {
+ Assign, /// (float& dest, float src) -> void
+
+ Select, /// (MetaArithmetic, bool pred, float a, float b) -> float
+
+ FAdd, /// (MetaArithmetic, float a, float b) -> float
+ FMul, /// (MetaArithmetic, float a, float b) -> float
+ FDiv, /// (MetaArithmetic, float a, float b) -> float
+ FFma, /// (MetaArithmetic, float a, float b, float c) -> float
+ FNegate, /// (MetaArithmetic, float a) -> float
+ FAbsolute, /// (MetaArithmetic, float a) -> float
+ FClamp, /// (MetaArithmetic, float value, float min, float max) -> float
+ FMin, /// (MetaArithmetic, float a, float b) -> float
+ FMax, /// (MetaArithmetic, float a, float b) -> float
+ FCos, /// (MetaArithmetic, float a) -> float
+ FSin, /// (MetaArithmetic, float a) -> float
+ FExp2, /// (MetaArithmetic, float a) -> float
+ FLog2, /// (MetaArithmetic, float a) -> float
+ FInverseSqrt, /// (MetaArithmetic, float a) -> float
+ FSqrt, /// (MetaArithmetic, float a) -> float
+ FRoundEven, /// (MetaArithmetic, float a) -> float
+ FFloor, /// (MetaArithmetic, float a) -> float
+ FCeil, /// (MetaArithmetic, float a) -> float
+ FTrunc, /// (MetaArithmetic, float a) -> float
+ FCastInteger, /// (MetaArithmetic, int a) -> float
+ FCastUInteger, /// (MetaArithmetic, uint a) -> float
+
+ IAdd, /// (MetaArithmetic, int a, int b) -> int
+ IMul, /// (MetaArithmetic, int a, int b) -> int
+ IDiv, /// (MetaArithmetic, int a, int b) -> int
+ INegate, /// (MetaArithmetic, int a) -> int
+ IAbsolute, /// (MetaArithmetic, int a) -> int
+ IMin, /// (MetaArithmetic, int a, int b) -> int
+ IMax, /// (MetaArithmetic, int a, int b) -> int
+ ICastFloat, /// (MetaArithmetic, float a) -> int
+ ICastUnsigned, /// (MetaArithmetic, uint a) -> int
+ ILogicalShiftLeft, /// (MetaArithmetic, int a, uint b) -> int
+ ILogicalShiftRight, /// (MetaArithmetic, int a, uint b) -> int
+ IArithmeticShiftRight, /// (MetaArithmetic, int a, uint b) -> int
+ IBitwiseAnd, /// (MetaArithmetic, int a, int b) -> int
+ IBitwiseOr, /// (MetaArithmetic, int a, int b) -> int
+ IBitwiseXor, /// (MetaArithmetic, int a, int b) -> int
+ IBitwiseNot, /// (MetaArithmetic, int a) -> int
+ IBitfieldInsert, /// (MetaArithmetic, int base, int insert, int offset, int bits) -> int
+ IBitfieldExtract, /// (MetaArithmetic, int value, int offset, int offset) -> int
+ IBitCount, /// (MetaArithmetic, int) -> int
+
+ UAdd, /// (MetaArithmetic, uint a, uint b) -> uint
+ UMul, /// (MetaArithmetic, uint a, uint b) -> uint
+ UDiv, /// (MetaArithmetic, uint a, uint b) -> uint
+ UMin, /// (MetaArithmetic, uint a, uint b) -> uint
+ UMax, /// (MetaArithmetic, uint a, uint b) -> uint
+ UCastFloat, /// (MetaArithmetic, float a) -> uint
+ UCastSigned, /// (MetaArithmetic, int a) -> uint
+ ULogicalShiftLeft, /// (MetaArithmetic, uint a, uint b) -> uint
+ ULogicalShiftRight, /// (MetaArithmetic, uint a, uint b) -> uint
+ UArithmeticShiftRight, /// (MetaArithmetic, uint a, uint b) -> uint
+ UBitwiseAnd, /// (MetaArithmetic, uint a, uint b) -> uint
+ UBitwiseOr, /// (MetaArithmetic, uint a, uint b) -> uint
+ UBitwiseXor, /// (MetaArithmetic, uint a, uint b) -> uint
+ UBitwiseNot, /// (MetaArithmetic, uint a) -> uint
+ UBitfieldInsert, /// (MetaArithmetic, uint base, uint insert, int offset, int bits) -> uint
+ UBitfieldExtract, /// (MetaArithmetic, uint value, int offset, int offset) -> uint
+ UBitCount, /// (MetaArithmetic, uint) -> uint
+
+ HAdd, /// (MetaArithmetic, f16vec2 a, f16vec2 b) -> f16vec2
+ HMul, /// (MetaArithmetic, f16vec2 a, f16vec2 b) -> f16vec2
+ HFma, /// (MetaArithmetic, f16vec2 a, f16vec2 b, f16vec2 c) -> f16vec2
+ HAbsolute, /// (f16vec2 a) -> f16vec2
+ HNegate, /// (f16vec2 a, bool first, bool second) -> f16vec2
+ HClamp, /// (f16vec2 src, float min, float max) -> f16vec2
+ HUnpack, /// (Tegra::Shader::HalfType, T value) -> f16vec2
+ HMergeF32, /// (f16vec2 src) -> float
+ HMergeH0, /// (f16vec2 dest, f16vec2 src) -> f16vec2
+ HMergeH1, /// (f16vec2 dest, f16vec2 src) -> f16vec2
+ HPack2, /// (float a, float b) -> f16vec2
+
+ LogicalAssign, /// (bool& dst, bool src) -> void
+ LogicalAnd, /// (bool a, bool b) -> bool
+ LogicalOr, /// (bool a, bool b) -> bool
+ LogicalXor, /// (bool a, bool b) -> bool
+ LogicalNegate, /// (bool a) -> bool
+ LogicalPick2, /// (bool2 pair, uint index) -> bool
+ LogicalAll2, /// (bool2 a) -> bool
+ LogicalAny2, /// (bool2 a) -> bool
+
+ LogicalFLessThan, /// (float a, float b) -> bool
+ LogicalFEqual, /// (float a, float b) -> bool
+ LogicalFLessEqual, /// (float a, float b) -> bool
+ LogicalFGreaterThan, /// (float a, float b) -> bool
+ LogicalFNotEqual, /// (float a, float b) -> bool
+ LogicalFGreaterEqual, /// (float a, float b) -> bool
+ LogicalFIsNan, /// (float a) -> bool
+
+ LogicalILessThan, /// (int a, int b) -> bool
+ LogicalIEqual, /// (int a, int b) -> bool
+ LogicalILessEqual, /// (int a, int b) -> bool
+ LogicalIGreaterThan, /// (int a, int b) -> bool
+ LogicalINotEqual, /// (int a, int b) -> bool
+ LogicalIGreaterEqual, /// (int a, int b) -> bool
+
+ LogicalULessThan, /// (uint a, uint b) -> bool
+ LogicalUEqual, /// (uint a, uint b) -> bool
+ LogicalULessEqual, /// (uint a, uint b) -> bool
+ LogicalUGreaterThan, /// (uint a, uint b) -> bool
+ LogicalUNotEqual, /// (uint a, uint b) -> bool
+ LogicalUGreaterEqual, /// (uint a, uint b) -> bool
+
+ Logical2HLessThan, /// (MetaHalfArithmetic, f16vec2 a, f16vec2) -> bool2
+ Logical2HEqual, /// (MetaHalfArithmetic, f16vec2 a, f16vec2) -> bool2
+ Logical2HLessEqual, /// (MetaHalfArithmetic, f16vec2 a, f16vec2) -> bool2
+ Logical2HGreaterThan, /// (MetaHalfArithmetic, f16vec2 a, f16vec2) -> bool2
+ Logical2HNotEqual, /// (MetaHalfArithmetic, f16vec2 a, f16vec2) -> bool2
+ Logical2HGreaterEqual, /// (MetaHalfArithmetic, f16vec2 a, f16vec2) -> bool2
+ Logical2HLessThanWithNan, /// (MetaHalfArithmetic, f16vec2 a, f16vec2) -> bool2
+ Logical2HEqualWithNan, /// (MetaHalfArithmetic, f16vec2 a, f16vec2) -> bool2
+ Logical2HLessEqualWithNan, /// (MetaHalfArithmetic, f16vec2 a, f16vec2) -> bool2
+ Logical2HGreaterThanWithNan, /// (MetaHalfArithmetic, f16vec2 a, f16vec2) -> bool2
+ Logical2HNotEqualWithNan, /// (MetaHalfArithmetic, f16vec2 a, f16vec2) -> bool2
+ Logical2HGreaterEqualWithNan, /// (MetaHalfArithmetic, f16vec2 a, f16vec2) -> bool2
+
+ Texture, /// (MetaTexture, float[N] coords) -> float4
+ TextureLod, /// (MetaTexture, float[N] coords) -> float4
+ TextureGather, /// (MetaTexture, float[N] coords) -> float4
+ TextureQueryDimensions, /// (MetaTexture, float a) -> float4
+ TextureQueryLod, /// (MetaTexture, float[N] coords) -> float4
+ TexelFetch, /// (MetaTexture, int[N], int) -> float4
+
+ Branch, /// (uint branch_target) -> void
+ PushFlowStack, /// (uint branch_target) -> void
+ PopFlowStack, /// () -> void
+ Exit, /// () -> void
+ Discard, /// () -> void
+
+ EmitVertex, /// () -> void
+ EndPrimitive, /// () -> void
+
+ YNegate, /// () -> float
+ LocalInvocationIdX, /// () -> uint
+ LocalInvocationIdY, /// () -> uint
+ LocalInvocationIdZ, /// () -> uint
+ WorkGroupIdX, /// () -> uint
+ WorkGroupIdY, /// () -> uint
+ WorkGroupIdZ, /// () -> uint
+
+ Amount,
+};
+
+enum class InternalFlag {
+ Zero = 0,
+ Sign = 1,
+ Carry = 2,
+ Overflow = 3,
+ Amount = 4,
+};
+
+enum class MetaStackClass {
+ Ssy,
+ Pbk,
+};
+
+class OperationNode;
+class ConditionalNode;
+class GprNode;
+class ImmediateNode;
+class InternalFlagNode;
+class PredicateNode;
+class AbufNode;
+class CbufNode;
+class LmemNode;
+class GmemNode;
+class CommentNode;
+
+using NodeData =
+ std::variant<OperationNode, ConditionalNode, GprNode, ImmediateNode, InternalFlagNode,
+ PredicateNode, AbufNode, CbufNode, LmemNode, GmemNode, CommentNode>;
+using Node = std::shared_ptr<NodeData>;
+using Node4 = std::array<Node, 4>;
+using NodeBlock = std::vector<Node>;
+
+class Sampler {
+public:
+ /// This constructor is for bound samplers
+ explicit Sampler(std::size_t offset, std::size_t index, Tegra::Shader::TextureType type,
+ bool is_array, bool is_shadow)
+ : offset{offset}, index{index}, type{type}, is_array{is_array}, is_shadow{is_shadow},
+ is_bindless{false} {}
+
+ /// This constructor is for bindless samplers
+ explicit Sampler(u32 cbuf_index, u32 cbuf_offset, std::size_t index,
+ Tegra::Shader::TextureType type, bool is_array, bool is_shadow)
+ : offset{(static_cast<u64>(cbuf_index) << 32) | cbuf_offset}, index{index}, type{type},
+ is_array{is_array}, is_shadow{is_shadow}, is_bindless{true} {}
+
+ /// This constructor is for serialization/deserialization
+ explicit Sampler(std::size_t offset, std::size_t index, Tegra::Shader::TextureType type,
+ bool is_array, bool is_shadow, bool is_bindless)
+ : offset{offset}, index{index}, type{type}, is_array{is_array}, is_shadow{is_shadow},
+ is_bindless{is_bindless} {}
+
+ std::size_t GetOffset() const {
+ return offset;
+ }
+
+ std::size_t GetIndex() const {
+ return index;
+ }
+
+ Tegra::Shader::TextureType GetType() const {
+ return type;
+ }
+
+ bool IsArray() const {
+ return is_array;
+ }
+
+ bool IsShadow() const {
+ return is_shadow;
+ }
+
+ bool IsBindless() const {
+ return is_bindless;
+ }
+
+ std::pair<u32, u32> GetBindlessCBuf() const {
+ return {static_cast<u32>(offset >> 32), static_cast<u32>(offset)};
+ }
+
+ bool operator<(const Sampler& rhs) const {
+ return std::tie(index, offset, type, is_array, is_shadow, is_bindless) <
+ std::tie(rhs.index, rhs.offset, rhs.type, rhs.is_array, rhs.is_shadow,
+ rhs.is_bindless);
+ }
+
+private:
+ /// Offset in TSC memory from which to read the sampler object, as specified by the sampling
+ /// instruction.
+ std::size_t offset{};
+ std::size_t index{}; ///< Value used to index into the generated GLSL sampler array.
+ Tegra::Shader::TextureType type{}; ///< The type used to sample this texture (Texture2D, etc)
+ bool is_array{}; ///< Whether the texture is being sampled as an array texture or not.
+ bool is_shadow{}; ///< Whether the texture is being sampled as a depth texture or not.
+ bool is_bindless{}; ///< Whether this sampler belongs to a bindless texture or not.
+};
+
+struct GlobalMemoryBase {
+ u32 cbuf_index{};
+ u32 cbuf_offset{};
+
+ bool operator<(const GlobalMemoryBase& rhs) const {
+ return std::tie(cbuf_index, cbuf_offset) < std::tie(rhs.cbuf_index, rhs.cbuf_offset);
+ }
+};
+
+/// Parameters describing an arithmetic operation
+struct MetaArithmetic {
+ bool precise{}; ///< Whether the operation can be constraint or not
+};
+
+/// Parameters describing a texture sampler
+struct MetaTexture {
+ const Sampler& sampler;
+ Node array;
+ Node depth_compare;
+ std::vector<Node> aoffi;
+ Node bias;
+ Node lod;
+ Node component{};
+ u32 element{};
+};
+
+/// Parameters that modify an operation but are not part of any particular operand
+using Meta = std::variant<MetaArithmetic, MetaTexture, MetaStackClass, Tegra::Shader::HalfType>;
+
+/// Holds any kind of operation that can be done in the IR
+class OperationNode final {
+public:
+ explicit OperationNode(OperationCode code) : OperationNode(code, Meta{}) {}
+
+ explicit OperationNode(OperationCode code, Meta meta)
+ : OperationNode(code, meta, std::vector<Node>{}) {}
+
+ 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)} {}
+
+ template <typename... Args>
+ explicit OperationNode(OperationCode code, Meta meta, Args&&... operands)
+ : code{code}, meta{std::move(meta)}, operands{operands...} {}
+
+ OperationCode GetCode() const {
+ return code;
+ }
+
+ const Meta& GetMeta() const {
+ return meta;
+ }
+
+ std::size_t GetOperandsCount() const {
+ return operands.size();
+ }
+
+ const Node& operator[](std::size_t operand_index) const {
+ return operands.at(operand_index);
+ }
+
+private:
+ OperationCode code{};
+ Meta meta{};
+ std::vector<Node> operands;
+};
+
+/// Encloses inside any kind of node that returns a boolean conditionally-executed code
+class ConditionalNode final {
+public:
+ explicit ConditionalNode(Node condition, std::vector<Node>&& code)
+ : condition{std::move(condition)}, code{std::move(code)} {}
+
+ const Node& GetCondition() const {
+ return condition;
+ }
+
+ const std::vector<Node>& GetCode() const {
+ return code;
+ }
+
+private:
+ Node condition; ///< Condition to be satisfied
+ std::vector<Node> code; ///< Code to execute
+};
+
+/// A general purpose register
+class GprNode final {
+public:
+ explicit constexpr GprNode(Tegra::Shader::Register index) : index{index} {}
+
+ u32 GetIndex() const {
+ return static_cast<u32>(index);
+ }
+
+private:
+ Tegra::Shader::Register index{};
+};
+
+/// A 32-bits value that represents an immediate value
+class ImmediateNode final {
+public:
+ explicit constexpr ImmediateNode(u32 value) : value{value} {}
+
+ u32 GetValue() const {
+ return value;
+ }
+
+private:
+ u32 value{};
+};
+
+/// One of Maxwell's internal flags
+class InternalFlagNode final {
+public:
+ explicit constexpr InternalFlagNode(InternalFlag flag) : flag{flag} {}
+
+ InternalFlag GetFlag() const {
+ return flag;
+ }
+
+private:
+ InternalFlag flag{};
+};
+
+/// 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} {}
+
+ Tegra::Shader::Pred GetIndex() const {
+ return index;
+ }
+
+ bool IsNegated() const {
+ return negated;
+ }
+
+private:
+ Tegra::Shader::Pred index{};
+ bool negated{};
+};
+
+/// Attribute buffer memory (known as attributes or varyings in GLSL terms)
+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} {}
+
+ // 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)} {}
+
+ Tegra::Shader::Attribute::Index GetIndex() const {
+ return index;
+ }
+
+ u32 GetElement() const {
+ return element;
+ }
+
+ const Node& GetBuffer() const {
+ return buffer;
+ }
+
+ bool IsPhysicalBuffer() const {
+ return static_cast<bool>(physical_address);
+ }
+
+ const Node& GetPhysicalAddress() const {
+ return physical_address;
+ }
+
+private:
+ Node physical_address;
+ Node buffer;
+ Tegra::Shader::Attribute::Index index{};
+ u32 element{};
+};
+
+/// 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)} {}
+
+ u32 GetIndex() const {
+ return index;
+ }
+
+ const Node& GetOffset() const {
+ return offset;
+ }
+
+private:
+ u32 index{};
+ Node offset;
+};
+
+/// Local memory node
+class LmemNode final {
+public:
+ explicit LmemNode(Node address) : address{std::move(address)} {}
+
+ const Node& GetAddress() const {
+ return address;
+ }
+
+private:
+ Node address;
+};
+
+/// 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} {}
+
+ const Node& GetRealAddress() const {
+ return real_address;
+ }
+
+ const Node& GetBaseAddress() const {
+ return base_address;
+ }
+
+ const GlobalMemoryBase& GetDescriptor() const {
+ return descriptor;
+ }
+
+private:
+ Node real_address;
+ Node base_address;
+ GlobalMemoryBase descriptor;
+};
+
+/// Commentary, can be dropped
+class CommentNode final {
+public:
+ explicit CommentNode(std::string text) : text{std::move(text)} {}
+
+ const std::string& GetText() const {
+ return text;
+ }
+
+private:
+ std::string text;
+};
+
+} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/node_helper.cpp b/src/video_core/shader/node_helper.cpp
new file mode 100644
index 000000000..6fccbbba3
--- /dev/null
+++ b/src/video_core/shader/node_helper.cpp
@@ -0,0 +1,99 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cstring>
+#include <vector>
+
+#include "common/common_types.h"
+#include "video_core/shader/node_helper.h"
+#include "video_core/shader/shader_ir.h"
+
+namespace VideoCommon::Shader {
+
+Node Conditional(Node condition, std::vector<Node> code) {
+ return MakeNode<ConditionalNode>(condition, std::move(code));
+}
+
+Node Comment(std::string text) {
+ return MakeNode<CommentNode>(std::move(text));
+}
+
+Node Immediate(u32 value) {
+ return MakeNode<ImmediateNode>(value);
+}
+
+Node Immediate(s32 value) {
+ return Immediate(static_cast<u32>(value));
+}
+
+Node Immediate(f32 value) {
+ u32 integral;
+ std::memcpy(&integral, &value, sizeof(u32));
+ return Immediate(integral);
+}
+
+OperationCode SignedToUnsignedCode(OperationCode operation_code, bool is_signed) {
+ if (is_signed) {
+ return operation_code;
+ }
+ switch (operation_code) {
+ case OperationCode::FCastInteger:
+ return OperationCode::FCastUInteger;
+ case OperationCode::IAdd:
+ return OperationCode::UAdd;
+ case OperationCode::IMul:
+ return OperationCode::UMul;
+ case OperationCode::IDiv:
+ return OperationCode::UDiv;
+ case OperationCode::IMin:
+ return OperationCode::UMin;
+ case OperationCode::IMax:
+ return OperationCode::UMax;
+ case OperationCode::ICastFloat:
+ return OperationCode::UCastFloat;
+ case OperationCode::ICastUnsigned:
+ return OperationCode::UCastSigned;
+ case OperationCode::ILogicalShiftLeft:
+ return OperationCode::ULogicalShiftLeft;
+ case OperationCode::ILogicalShiftRight:
+ return OperationCode::ULogicalShiftRight;
+ case OperationCode::IArithmeticShiftRight:
+ return OperationCode::UArithmeticShiftRight;
+ case OperationCode::IBitwiseAnd:
+ return OperationCode::UBitwiseAnd;
+ case OperationCode::IBitwiseOr:
+ return OperationCode::UBitwiseOr;
+ case OperationCode::IBitwiseXor:
+ return OperationCode::UBitwiseXor;
+ case OperationCode::IBitwiseNot:
+ return OperationCode::UBitwiseNot;
+ case OperationCode::IBitfieldInsert:
+ return OperationCode::UBitfieldInsert;
+ case OperationCode::IBitCount:
+ return OperationCode::UBitCount;
+ case OperationCode::LogicalILessThan:
+ return OperationCode::LogicalULessThan;
+ case OperationCode::LogicalIEqual:
+ return OperationCode::LogicalUEqual;
+ case OperationCode::LogicalILessEqual:
+ return OperationCode::LogicalULessEqual;
+ case OperationCode::LogicalIGreaterThan:
+ return OperationCode::LogicalUGreaterThan;
+ case OperationCode::LogicalINotEqual:
+ return OperationCode::LogicalUNotEqual;
+ case OperationCode::LogicalIGreaterEqual:
+ return OperationCode::LogicalUGreaterEqual;
+ case OperationCode::INegate:
+ UNREACHABLE_MSG("Can't negate an unsigned integer");
+ return {};
+ case OperationCode::IAbsolute:
+ UNREACHABLE_MSG("Can't apply absolute to an unsigned integer");
+ return {};
+ default:
+ UNREACHABLE_MSG("Unknown signed operation with code={}", static_cast<u32>(operation_code));
+ return {};
+ }
+}
+
+} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/node_helper.h b/src/video_core/shader/node_helper.h
new file mode 100644
index 000000000..0c2aa749b
--- /dev/null
+++ b/src/video_core/shader/node_helper.h
@@ -0,0 +1,65 @@
+// 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>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#include "common/common_types.h"
+#include "video_core/shader/node.h"
+
+namespace VideoCommon::Shader {
+
+/// This arithmetic operation cannot be constraint
+inline constexpr MetaArithmetic PRECISE = {true};
+/// This arithmetic operation can be optimized away
+inline constexpr MetaArithmetic NO_PRECISE = {false};
+
+/// Creates a conditional node
+Node Conditional(Node condition, std::vector<Node> code);
+
+/// Creates a commentary node
+Node Comment(std::string text);
+
+/// Creates an u32 immediate
+Node Immediate(u32 value);
+
+/// Creates a s32 immediate
+Node Immediate(s32 value);
+
+/// Creates a f32 immediate
+Node Immediate(f32 value);
+
+/// Converts an signed operation code to an unsigned operation code
+OperationCode SignedToUnsignedCode(OperationCode operation_code, bool is_signed);
+
+template <typename T, typename... Args>
+Node MakeNode(Args&&... args) {
+ static_assert(std::is_convertible_v<T, NodeData>);
+ return std::make_shared<NodeData>(T(std::forward<Args>(args)...));
+}
+
+template <typename... Args>
+Node Operation(OperationCode code, Args&&... args) {
+ if constexpr (sizeof...(args) == 0) {
+ return MakeNode<OperationNode>(code);
+ } else if constexpr (std::is_convertible_v<std::tuple_element_t<0, std::tuple<Args...>>,
+ Meta>) {
+ return MakeNode<OperationNode>(code, std::forward<Args>(args)...);
+ } else {
+ return MakeNode<OperationNode>(code, Meta{}, std::forward<Args>(args)...);
+ }
+}
+
+template <typename... Args>
+Node SignedOperation(OperationCode code, bool is_signed, Args&&... args) {
+ return Operation(SignedToUnsignedCode(code, is_signed), std::forward<Args>(args)...);
+}
+
+} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/shader_ir.cpp b/src/video_core/shader/shader_ir.cpp
index ac5112d78..11b545cca 100644
--- a/src/video_core/shader/shader_ir.cpp
+++ b/src/video_core/shader/shader_ir.cpp
@@ -9,6 +9,7 @@
#include "common/common_types.h"
#include "common/logging/log.h"
#include "video_core/engines/shader_bytecode.h"
+#include "video_core/shader/node_helper.h"
#include "video_core/shader/shader_ir.h"
namespace VideoCommon::Shader {
@@ -21,30 +22,18 @@ using Tegra::Shader::PredCondition;
using Tegra::Shader::PredOperation;
using Tegra::Shader::Register;
-Node ShaderIR::StoreNode(NodeData&& node_data) {
- auto store = std::make_unique<NodeData>(node_data);
- const Node node = store.get();
- stored_nodes.push_back(std::move(store));
- return node;
-}
-
-Node ShaderIR::Conditional(Node condition, std::vector<Node>&& code) {
- return StoreNode(ConditionalNode(condition, std::move(code)));
+ShaderIR::ShaderIR(const ProgramCode& program_code, u32 main_offset)
+ : program_code{program_code}, main_offset{main_offset} {
+ Decode();
}
-Node ShaderIR::Comment(const std::string& text) {
- return StoreNode(CommentNode(text));
-}
-
-Node ShaderIR::Immediate(u32 value) {
- return StoreNode(ImmediateNode(value));
-}
+ShaderIR::~ShaderIR() = default;
Node ShaderIR::GetRegister(Register reg) {
if (reg != Register::ZeroIndex) {
used_registers.insert(static_cast<u32>(reg));
}
- return StoreNode(GprNode(reg));
+ return MakeNode<GprNode>(reg);
}
Node ShaderIR::GetImmediate19(Instruction instr) {
@@ -62,7 +51,7 @@ Node ShaderIR::GetConstBuffer(u64 index_, u64 offset_) {
const auto [entry, is_new] = used_cbufs.try_emplace(index);
entry->second.MarkAsUsed(offset);
- return StoreNode(CbufNode(index, Immediate(offset)));
+ return MakeNode<CbufNode>(index, Immediate(offset));
}
Node ShaderIR::GetConstBufferIndirect(u64 index_, u64 offset_, Node node) {
@@ -73,7 +62,7 @@ Node ShaderIR::GetConstBufferIndirect(u64 index_, u64 offset_, Node node) {
entry->second.MarkAsUsedIndirect();
const Node final_offset = Operation(OperationCode::UAdd, NO_PRECISE, node, Immediate(offset));
- return StoreNode(CbufNode(index, final_offset));
+ return MakeNode<CbufNode>(index, final_offset);
}
Node ShaderIR::GetPredicate(u64 pred_, bool negated) {
@@ -82,20 +71,21 @@ Node ShaderIR::GetPredicate(u64 pred_, bool negated) {
used_predicates.insert(pred);
}
- return StoreNode(PredicateNode(pred, negated));
+ return MakeNode<PredicateNode>(pred, negated);
}
Node ShaderIR::GetPredicate(bool immediate) {
return GetPredicate(static_cast<u64>(immediate ? Pred::UnusedIndex : Pred::NeverExecute));
}
-Node ShaderIR::GetInputAttribute(Attribute::Index index, u64 element,
- const Tegra::Shader::IpaMode& input_mode, Node buffer) {
- const auto [entry, is_new] =
- used_input_attributes.emplace(std::make_pair(index, std::set<Tegra::Shader::IpaMode>{}));
- entry->second.insert(input_mode);
+Node ShaderIR::GetInputAttribute(Attribute::Index index, u64 element, Node buffer) {
+ used_input_attributes.emplace(index);
+ return MakeNode<AbufNode>(index, static_cast<u32>(element), buffer);
+}
- return StoreNode(AbufNode(index, static_cast<u32>(element), input_mode, buffer));
+Node ShaderIR::GetPhysicalInputAttribute(Tegra::Shader::Register physical_address, Node buffer) {
+ uses_physical_attributes = true;
+ return MakeNode<AbufNode>(GetRegister(physical_address), buffer);
}
Node ShaderIR::GetOutputAttribute(Attribute::Index index, u64 element, Node buffer) {
@@ -107,11 +97,11 @@ Node ShaderIR::GetOutputAttribute(Attribute::Index index, u64 element, Node buff
}
used_output_attributes.insert(index);
- return StoreNode(AbufNode(index, static_cast<u32>(element), buffer));
+ return MakeNode<AbufNode>(index, static_cast<u32>(element), buffer);
}
Node ShaderIR::GetInternalFlag(InternalFlag flag, bool negated) {
- const Node node = StoreNode(InternalFlagNode(flag));
+ const Node node = MakeNode<InternalFlagNode>(flag);
if (negated) {
return Operation(OperationCode::LogicalNegate, node);
}
@@ -119,7 +109,7 @@ Node ShaderIR::GetInternalFlag(InternalFlag flag, bool negated) {
}
Node ShaderIR::GetLocalMemory(Node address) {
- return StoreNode(LmemNode(address));
+ return MakeNode<LmemNode>(address);
}
Node ShaderIR::GetTemporal(u32 id) {
@@ -189,7 +179,11 @@ Node ShaderIR::UnpackHalfImmediate(Instruction instr, bool has_negation) {
const Node first_negate = GetPredicate(instr.half_imm.first_negate != 0);
const Node second_negate = GetPredicate(instr.half_imm.second_negate != 0);
- return Operation(OperationCode::HNegate, HALF_NO_PRECISE, value, first_negate, second_negate);
+ return Operation(OperationCode::HNegate, NO_PRECISE, value, first_negate, second_negate);
+}
+
+Node ShaderIR::UnpackHalfFloat(Node value, Tegra::Shader::HalfType type) {
+ return Operation(OperationCode::HUnpack, type, value);
}
Node ShaderIR::HalfMerge(Node dest, Node src, Tegra::Shader::HalfMerge merge) {
@@ -209,17 +203,26 @@ Node ShaderIR::HalfMerge(Node dest, Node src, Tegra::Shader::HalfMerge merge) {
Node ShaderIR::GetOperandAbsNegHalf(Node value, bool absolute, bool negate) {
if (absolute) {
- value = Operation(OperationCode::HAbsolute, HALF_NO_PRECISE, value);
+ value = Operation(OperationCode::HAbsolute, NO_PRECISE, value);
}
if (negate) {
- value = Operation(OperationCode::HNegate, HALF_NO_PRECISE, value, GetPredicate(true),
+ value = Operation(OperationCode::HNegate, NO_PRECISE, value, GetPredicate(true),
GetPredicate(true));
}
return value;
}
+Node ShaderIR::GetSaturatedHalfFloat(Node value, bool saturate) {
+ if (!saturate) {
+ return value;
+ }
+ const Node positive_zero = Immediate(std::copysignf(0, 1));
+ const Node positive_one = Immediate(1.0f);
+ return Operation(OperationCode::HClamp, NO_PRECISE, value, positive_zero, positive_one);
+}
+
Node ShaderIR::GetPredicateComparisonFloat(PredCondition condition, Node op_a, Node op_b) {
- static const std::unordered_map<PredCondition, OperationCode> PredicateComparisonTable = {
+ const std::unordered_map<PredCondition, OperationCode> PredicateComparisonTable = {
{PredCondition::LessThan, OperationCode::LogicalFLessThan},
{PredCondition::Equal, OperationCode::LogicalFEqual},
{PredCondition::LessEqual, OperationCode::LogicalFLessEqual},
@@ -255,7 +258,7 @@ Node ShaderIR::GetPredicateComparisonFloat(PredCondition condition, Node op_a, N
Node ShaderIR::GetPredicateComparisonInteger(PredCondition condition, bool is_signed, Node op_a,
Node op_b) {
- static const std::unordered_map<PredCondition, OperationCode> PredicateComparisonTable = {
+ const std::unordered_map<PredCondition, OperationCode> PredicateComparisonTable = {
{PredCondition::LessThan, OperationCode::LogicalILessThan},
{PredCondition::Equal, OperationCode::LogicalIEqual},
{PredCondition::LessEqual, OperationCode::LogicalILessEqual},
@@ -283,40 +286,32 @@ Node ShaderIR::GetPredicateComparisonInteger(PredCondition condition, bool is_si
return predicate;
}
-Node ShaderIR::GetPredicateComparisonHalf(Tegra::Shader::PredCondition condition,
- const MetaHalfArithmetic& meta, Node op_a, Node op_b) {
-
- UNIMPLEMENTED_IF_MSG(condition == PredCondition::LessThanWithNan ||
- condition == PredCondition::NotEqualWithNan ||
- condition == PredCondition::LessEqualWithNan ||
- condition == PredCondition::GreaterThanWithNan ||
- condition == PredCondition::GreaterEqualWithNan,
- "Unimplemented NaN comparison for half floats");
-
- static const std::unordered_map<PredCondition, OperationCode> PredicateComparisonTable = {
+Node ShaderIR::GetPredicateComparisonHalf(Tegra::Shader::PredCondition condition, Node op_a,
+ Node op_b) {
+ const std::unordered_map<PredCondition, OperationCode> PredicateComparisonTable = {
{PredCondition::LessThan, OperationCode::Logical2HLessThan},
{PredCondition::Equal, OperationCode::Logical2HEqual},
{PredCondition::LessEqual, OperationCode::Logical2HLessEqual},
{PredCondition::GreaterThan, OperationCode::Logical2HGreaterThan},
{PredCondition::NotEqual, OperationCode::Logical2HNotEqual},
{PredCondition::GreaterEqual, OperationCode::Logical2HGreaterEqual},
- {PredCondition::LessThanWithNan, OperationCode::Logical2HLessThan},
- {PredCondition::NotEqualWithNan, OperationCode::Logical2HNotEqual},
- {PredCondition::LessEqualWithNan, OperationCode::Logical2HLessEqual},
- {PredCondition::GreaterThanWithNan, OperationCode::Logical2HGreaterThan},
- {PredCondition::GreaterEqualWithNan, OperationCode::Logical2HGreaterEqual}};
+ {PredCondition::LessThanWithNan, OperationCode::Logical2HLessThanWithNan},
+ {PredCondition::NotEqualWithNan, OperationCode::Logical2HNotEqualWithNan},
+ {PredCondition::LessEqualWithNan, OperationCode::Logical2HLessEqualWithNan},
+ {PredCondition::GreaterThanWithNan, OperationCode::Logical2HGreaterThanWithNan},
+ {PredCondition::GreaterEqualWithNan, OperationCode::Logical2HGreaterEqualWithNan}};
const auto comparison{PredicateComparisonTable.find(condition)};
UNIMPLEMENTED_IF_MSG(comparison == PredicateComparisonTable.end(),
"Unknown predicate comparison operation");
- const Node predicate = Operation(comparison->second, meta, op_a, op_b);
+ const Node predicate = Operation(comparison->second, NO_PRECISE, op_a, op_b);
return predicate;
}
OperationCode ShaderIR::GetPredicateCombiner(PredOperation operation) {
- static const std::unordered_map<PredOperation, OperationCode> PredicateOperationTable = {
+ const std::unordered_map<PredOperation, OperationCode> PredicateOperationTable = {
{PredOperation::And, OperationCode::LogicalAnd},
{PredOperation::Or, OperationCode::LogicalOr},
{PredOperation::Xor, OperationCode::LogicalXor},
@@ -380,65 +375,4 @@ Node ShaderIR::BitfieldExtract(Node value, u32 offset, u32 bits) {
Immediate(bits));
}
-/*static*/ OperationCode ShaderIR::SignedToUnsignedCode(OperationCode operation_code,
- bool is_signed) {
- if (is_signed) {
- return operation_code;
- }
- switch (operation_code) {
- case OperationCode::FCastInteger:
- return OperationCode::FCastUInteger;
- case OperationCode::IAdd:
- return OperationCode::UAdd;
- case OperationCode::IMul:
- return OperationCode::UMul;
- case OperationCode::IDiv:
- return OperationCode::UDiv;
- case OperationCode::IMin:
- return OperationCode::UMin;
- case OperationCode::IMax:
- return OperationCode::UMax;
- case OperationCode::ICastFloat:
- return OperationCode::UCastFloat;
- case OperationCode::ICastUnsigned:
- return OperationCode::UCastSigned;
- case OperationCode::ILogicalShiftLeft:
- return OperationCode::ULogicalShiftLeft;
- case OperationCode::ILogicalShiftRight:
- return OperationCode::ULogicalShiftRight;
- case OperationCode::IArithmeticShiftRight:
- return OperationCode::UArithmeticShiftRight;
- case OperationCode::IBitwiseAnd:
- return OperationCode::UBitwiseAnd;
- case OperationCode::IBitwiseOr:
- return OperationCode::UBitwiseOr;
- case OperationCode::IBitwiseXor:
- return OperationCode::UBitwiseXor;
- case OperationCode::IBitwiseNot:
- return OperationCode::UBitwiseNot;
- case OperationCode::IBitfieldInsert:
- return OperationCode::UBitfieldInsert;
- case OperationCode::IBitCount:
- return OperationCode::UBitCount;
- case OperationCode::LogicalILessThan:
- return OperationCode::LogicalULessThan;
- case OperationCode::LogicalIEqual:
- return OperationCode::LogicalUEqual;
- case OperationCode::LogicalILessEqual:
- return OperationCode::LogicalULessEqual;
- case OperationCode::LogicalIGreaterThan:
- return OperationCode::LogicalUGreaterThan;
- case OperationCode::LogicalINotEqual:
- return OperationCode::LogicalUNotEqual;
- case OperationCode::LogicalIGreaterEqual:
- return OperationCode::LogicalUGreaterEqual;
- case OperationCode::INegate:
- UNREACHABLE_MSG("Can't negate an unsigned integer");
- case OperationCode::IAbsolute:
- UNREACHABLE_MSG("Can't apply absolute to an unsigned integer");
- }
- UNREACHABLE_MSG("Unknown signed operation with code={}", static_cast<u32>(operation_code));
- return {};
-}
-
-} // namespace VideoCommon::Shader \ No newline at end of file
+} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/shader_ir.h b/src/video_core/shader/shader_ir.h
index 52c7f2c4e..edcf2288e 100644
--- a/src/video_core/shader/shader_ir.h
+++ b/src/video_core/shader/shader_ir.h
@@ -7,6 +7,7 @@
#include <array>
#include <cstring>
#include <map>
+#include <optional>
#include <set>
#include <string>
#include <tuple>
@@ -17,174 +18,14 @@
#include "video_core/engines/maxwell_3d.h"
#include "video_core/engines/shader_bytecode.h"
#include "video_core/engines/shader_header.h"
+#include "video_core/shader/node.h"
namespace VideoCommon::Shader {
-class OperationNode;
-class ConditionalNode;
-class GprNode;
-class ImmediateNode;
-class InternalFlagNode;
-class PredicateNode;
-class AbufNode; ///< Attribute buffer
-class CbufNode; ///< Constant buffer
-class LmemNode; ///< Local memory
-class GmemNode; ///< Global memory
-class CommentNode;
-
using ProgramCode = std::vector<u64>;
-using NodeData =
- std::variant<OperationNode, ConditionalNode, GprNode, ImmediateNode, InternalFlagNode,
- PredicateNode, AbufNode, CbufNode, LmemNode, GmemNode, CommentNode>;
-using Node = const NodeData*;
-using Node4 = std::array<Node, 4>;
-using NodeBlock = std::vector<Node>;
-
constexpr u32 MAX_PROGRAM_LENGTH = 0x1000;
-enum class OperationCode {
- Assign, /// (float& dest, float src) -> void
-
- Select, /// (MetaArithmetic, bool pred, float a, float b) -> float
-
- FAdd, /// (MetaArithmetic, float a, float b) -> float
- FMul, /// (MetaArithmetic, float a, float b) -> float
- FDiv, /// (MetaArithmetic, float a, float b) -> float
- FFma, /// (MetaArithmetic, float a, float b, float c) -> float
- FNegate, /// (MetaArithmetic, float a) -> float
- FAbsolute, /// (MetaArithmetic, float a) -> float
- FClamp, /// (MetaArithmetic, float value, float min, float max) -> float
- FMin, /// (MetaArithmetic, float a, float b) -> float
- FMax, /// (MetaArithmetic, float a, float b) -> float
- FCos, /// (MetaArithmetic, float a) -> float
- FSin, /// (MetaArithmetic, float a) -> float
- FExp2, /// (MetaArithmetic, float a) -> float
- FLog2, /// (MetaArithmetic, float a) -> float
- FInverseSqrt, /// (MetaArithmetic, float a) -> float
- FSqrt, /// (MetaArithmetic, float a) -> float
- FRoundEven, /// (MetaArithmetic, float a) -> float
- FFloor, /// (MetaArithmetic, float a) -> float
- FCeil, /// (MetaArithmetic, float a) -> float
- FTrunc, /// (MetaArithmetic, float a) -> float
- FCastInteger, /// (MetaArithmetic, int a) -> float
- FCastUInteger, /// (MetaArithmetic, uint a) -> float
-
- IAdd, /// (MetaArithmetic, int a, int b) -> int
- IMul, /// (MetaArithmetic, int a, int b) -> int
- IDiv, /// (MetaArithmetic, int a, int b) -> int
- INegate, /// (MetaArithmetic, int a) -> int
- IAbsolute, /// (MetaArithmetic, int a) -> int
- IMin, /// (MetaArithmetic, int a, int b) -> int
- IMax, /// (MetaArithmetic, int a, int b) -> int
- ICastFloat, /// (MetaArithmetic, float a) -> int
- ICastUnsigned, /// (MetaArithmetic, uint a) -> int
- ILogicalShiftLeft, /// (MetaArithmetic, int a, uint b) -> int
- ILogicalShiftRight, /// (MetaArithmetic, int a, uint b) -> int
- IArithmeticShiftRight, /// (MetaArithmetic, int a, uint b) -> int
- IBitwiseAnd, /// (MetaArithmetic, int a, int b) -> int
- IBitwiseOr, /// (MetaArithmetic, int a, int b) -> int
- IBitwiseXor, /// (MetaArithmetic, int a, int b) -> int
- IBitwiseNot, /// (MetaArithmetic, int a) -> int
- IBitfieldInsert, /// (MetaArithmetic, int base, int insert, int offset, int bits) -> int
- IBitfieldExtract, /// (MetaArithmetic, int value, int offset, int offset) -> int
- IBitCount, /// (MetaArithmetic, int) -> int
-
- UAdd, /// (MetaArithmetic, uint a, uint b) -> uint
- UMul, /// (MetaArithmetic, uint a, uint b) -> uint
- UDiv, /// (MetaArithmetic, uint a, uint b) -> uint
- UMin, /// (MetaArithmetic, uint a, uint b) -> uint
- UMax, /// (MetaArithmetic, uint a, uint b) -> uint
- UCastFloat, /// (MetaArithmetic, float a) -> uint
- UCastSigned, /// (MetaArithmetic, int a) -> uint
- ULogicalShiftLeft, /// (MetaArithmetic, uint a, uint b) -> uint
- ULogicalShiftRight, /// (MetaArithmetic, uint a, uint b) -> uint
- UArithmeticShiftRight, /// (MetaArithmetic, uint a, uint b) -> uint
- UBitwiseAnd, /// (MetaArithmetic, uint a, uint b) -> uint
- UBitwiseOr, /// (MetaArithmetic, uint a, uint b) -> uint
- UBitwiseXor, /// (MetaArithmetic, uint a, uint b) -> uint
- UBitwiseNot, /// (MetaArithmetic, uint a) -> uint
- UBitfieldInsert, /// (MetaArithmetic, uint base, uint insert, int offset, int bits) -> uint
- UBitfieldExtract, /// (MetaArithmetic, uint value, int offset, int offset) -> uint
- UBitCount, /// (MetaArithmetic, uint) -> uint
-
- HAdd, /// (MetaHalfArithmetic, f16vec2 a, f16vec2 b) -> f16vec2
- HMul, /// (MetaHalfArithmetic, f16vec2 a, f16vec2 b) -> f16vec2
- HFma, /// (MetaHalfArithmetic, f16vec2 a, f16vec2 b, f16vec2 c) -> f16vec2
- HAbsolute, /// (f16vec2 a) -> f16vec2
- HNegate, /// (f16vec2 a, bool first, bool second) -> f16vec2
- HMergeF32, /// (f16vec2 src) -> float
- HMergeH0, /// (f16vec2 dest, f16vec2 src) -> f16vec2
- HMergeH1, /// (f16vec2 dest, f16vec2 src) -> f16vec2
- HPack2, /// (float a, float b) -> f16vec2
-
- LogicalAssign, /// (bool& dst, bool src) -> void
- LogicalAnd, /// (bool a, bool b) -> bool
- LogicalOr, /// (bool a, bool b) -> bool
- LogicalXor, /// (bool a, bool b) -> bool
- LogicalNegate, /// (bool a) -> bool
- LogicalPick2, /// (bool2 pair, uint index) -> bool
- LogicalAll2, /// (bool2 a) -> bool
- LogicalAny2, /// (bool2 a) -> bool
-
- LogicalFLessThan, /// (float a, float b) -> bool
- LogicalFEqual, /// (float a, float b) -> bool
- LogicalFLessEqual, /// (float a, float b) -> bool
- LogicalFGreaterThan, /// (float a, float b) -> bool
- LogicalFNotEqual, /// (float a, float b) -> bool
- LogicalFGreaterEqual, /// (float a, float b) -> bool
- LogicalFIsNan, /// (float a) -> bool
-
- LogicalILessThan, /// (int a, int b) -> bool
- LogicalIEqual, /// (int a, int b) -> bool
- LogicalILessEqual, /// (int a, int b) -> bool
- LogicalIGreaterThan, /// (int a, int b) -> bool
- LogicalINotEqual, /// (int a, int b) -> bool
- LogicalIGreaterEqual, /// (int a, int b) -> bool
-
- LogicalULessThan, /// (uint a, uint b) -> bool
- LogicalUEqual, /// (uint a, uint b) -> bool
- LogicalULessEqual, /// (uint a, uint b) -> bool
- LogicalUGreaterThan, /// (uint a, uint b) -> bool
- LogicalUNotEqual, /// (uint a, uint b) -> bool
- LogicalUGreaterEqual, /// (uint a, uint b) -> bool
-
- Logical2HLessThan, /// (MetaHalfArithmetic, f16vec2 a, f16vec2) -> bool2
- Logical2HEqual, /// (MetaHalfArithmetic, f16vec2 a, f16vec2) -> bool2
- Logical2HLessEqual, /// (MetaHalfArithmetic, f16vec2 a, f16vec2) -> bool2
- Logical2HGreaterThan, /// (MetaHalfArithmetic, f16vec2 a, f16vec2) -> bool2
- Logical2HNotEqual, /// (MetaHalfArithmetic, f16vec2 a, f16vec2) -> bool2
- Logical2HGreaterEqual, /// (MetaHalfArithmetic, f16vec2 a, f16vec2) -> bool2
-
- Texture, /// (MetaTexture, float[N] coords) -> float4
- TextureLod, /// (MetaTexture, float[N] coords) -> float4
- TextureGather, /// (MetaTexture, float[N] coords) -> float4
- TextureQueryDimensions, /// (MetaTexture, float a) -> float4
- TextureQueryLod, /// (MetaTexture, float[N] coords) -> float4
- TexelFetch, /// (MetaTexture, int[N], int) -> float4
-
- Branch, /// (uint branch_target) -> void
- PushFlowStack, /// (uint branch_target) -> void
- PopFlowStack, /// () -> void
- Exit, /// () -> void
- Discard, /// () -> void
-
- EmitVertex, /// () -> void
- EndPrimitive, /// () -> void
-
- YNegate, /// () -> float
-
- Amount,
-};
-
-enum class InternalFlag {
- Zero = 0,
- Sign = 1,
- Carry = 2,
- Overflow = 3,
- Amount = 4,
-};
-
/// Describes the behaviour of code path of a given entry point and a return point.
enum class ExitMethod {
Undetermined, ///< Internal value. Only occur when analyzing JMP loop.
@@ -193,47 +34,6 @@ enum class ExitMethod {
AlwaysEnd, ///< All code paths reach a END instruction.
};
-class Sampler {
-public:
- explicit Sampler(std::size_t offset, std::size_t index, Tegra::Shader::TextureType type,
- bool is_array, bool is_shadow)
- : offset{offset}, index{index}, type{type}, is_array{is_array}, is_shadow{is_shadow} {}
-
- std::size_t GetOffset() const {
- return offset;
- }
-
- std::size_t GetIndex() const {
- return index;
- }
-
- Tegra::Shader::TextureType GetType() const {
- return type;
- }
-
- bool IsArray() const {
- return is_array;
- }
-
- bool IsShadow() const {
- return is_shadow;
- }
-
- bool operator<(const Sampler& rhs) const {
- return std::tie(offset, index, type, is_array, is_shadow) <
- std::tie(rhs.offset, rhs.index, rhs.type, rhs.is_array, rhs.is_shadow);
- }
-
-private:
- /// Offset in TSC memory from which to read the sampler object, as specified by the sampling
- /// instruction.
- std::size_t offset{};
- std::size_t index{}; ///< Value used to index into the generated GLSL sampler array.
- Tegra::Shader::TextureType type{}; ///< The type used to sample this texture (Texture2D, etc)
- bool is_array{}; ///< Whether the texture is being sampled as an array texture or not.
- bool is_shadow{}; ///< Whether the texture is being sampled as a depth texture or not.
-};
-
class ConstBuffer {
public:
explicit ConstBuffer(u32 max_offset, bool is_indirect)
@@ -266,279 +66,15 @@ private:
bool is_indirect{};
};
-struct GlobalMemoryBase {
- u32 cbuf_index{};
- u32 cbuf_offset{};
-
- bool operator<(const GlobalMemoryBase& rhs) const {
- return std::tie(cbuf_index, cbuf_offset) < std::tie(rhs.cbuf_index, rhs.cbuf_offset);
- }
-};
-
-struct MetaArithmetic {
- bool precise{};
-};
-
-struct MetaHalfArithmetic {
- bool precise{};
- std::array<Tegra::Shader::HalfType, 3> types = {Tegra::Shader::HalfType::H0_H1,
- Tegra::Shader::HalfType::H0_H1,
- Tegra::Shader::HalfType::H0_H1};
-};
-
-struct MetaTexture {
- const Sampler& sampler;
- Node array{};
- Node depth_compare{};
- std::vector<Node> extras;
- u32 element{};
-};
-
-constexpr MetaArithmetic PRECISE = {true};
-constexpr MetaArithmetic NO_PRECISE = {false};
-constexpr MetaHalfArithmetic HALF_NO_PRECISE = {false};
-
-using Meta = std::variant<MetaArithmetic, MetaHalfArithmetic, MetaTexture>;
-
-/// Holds any kind of operation that can be done in the IR
-class OperationNode final {
-public:
- template <typename... T>
- explicit constexpr OperationNode(OperationCode code) : code{code}, meta{} {}
-
- template <typename... T>
- explicit constexpr OperationNode(OperationCode code, Meta&& meta)
- : code{code}, meta{std::move(meta)} {}
-
- template <typename... T>
- explicit constexpr OperationNode(OperationCode code, const T*... operands)
- : OperationNode(code, {}, operands...) {}
-
- template <typename... T>
- explicit constexpr OperationNode(OperationCode code, Meta&& meta, const T*... operands_)
- : code{code}, meta{std::move(meta)} {
-
- auto operands_list = {operands_...};
- for (auto& operand : operands_list) {
- operands.push_back(operand);
- }
- }
-
- explicit OperationNode(OperationCode code, Meta&& meta, std::vector<Node>&& operands)
- : code{code}, meta{meta}, operands{std::move(operands)} {}
-
- explicit OperationNode(OperationCode code, std::vector<Node>&& operands)
- : code{code}, meta{}, operands{std::move(operands)} {}
-
- OperationCode GetCode() const {
- return code;
- }
-
- const Meta& GetMeta() const {
- return meta;
- }
-
- std::size_t GetOperandsCount() const {
- return operands.size();
- }
-
- Node operator[](std::size_t operand_index) const {
- return operands.at(operand_index);
- }
-
-private:
- const OperationCode code;
- const Meta meta;
- std::vector<Node> operands;
-};
-
-/// Encloses inside any kind of node that returns a boolean conditionally-executed code
-class ConditionalNode final {
-public:
- explicit ConditionalNode(Node condition, std::vector<Node>&& code)
- : condition{condition}, code{std::move(code)} {}
-
- Node GetCondition() const {
- return condition;
- }
-
- const std::vector<Node>& GetCode() const {
- return code;
- }
-
-private:
- const Node condition; ///< Condition to be satisfied
- std::vector<Node> code; ///< Code to execute
-};
-
-/// A general purpose register
-class GprNode final {
-public:
- explicit constexpr GprNode(Tegra::Shader::Register index) : index{index} {}
-
- u32 GetIndex() const {
- return static_cast<u32>(index);
- }
-
-private:
- const Tegra::Shader::Register index;
-};
-
-/// A 32-bits value that represents an immediate value
-class ImmediateNode final {
-public:
- explicit constexpr ImmediateNode(u32 value) : value{value} {}
-
- u32 GetValue() const {
- return value;
- }
-
-private:
- const u32 value;
-};
-
-/// One of Maxwell's internal flags
-class InternalFlagNode final {
-public:
- explicit constexpr InternalFlagNode(InternalFlag flag) : flag{flag} {}
-
- InternalFlag GetFlag() const {
- return flag;
- }
-
-private:
- const InternalFlag flag;
-};
-
-/// 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} {}
-
- Tegra::Shader::Pred GetIndex() const {
- return index;
- }
-
- bool IsNegated() const {
- return negated;
- }
-
-private:
- const Tegra::Shader::Pred index;
- const bool negated;
-};
-
-/// Attribute buffer memory (known as attributes or varyings in GLSL terms)
-class AbufNode final {
-public:
- explicit constexpr AbufNode(Tegra::Shader::Attribute::Index index, u32 element,
- const Tegra::Shader::IpaMode& input_mode, Node buffer = {})
- : input_mode{input_mode}, buffer{buffer}, index{index}, element{element} {}
-
- explicit constexpr AbufNode(Tegra::Shader::Attribute::Index index, u32 element,
- Node buffer = {})
- : input_mode{}, buffer{buffer}, index{index}, element{element} {}
-
- Tegra::Shader::IpaMode GetInputMode() const {
- return input_mode;
- }
-
- Tegra::Shader::Attribute::Index GetIndex() const {
- return index;
- }
-
- u32 GetElement() const {
- return element;
- }
-
- Node GetBuffer() const {
- return buffer;
- }
-
-private:
- const Tegra::Shader::IpaMode input_mode;
- const Node buffer;
- const Tegra::Shader::Attribute::Index index;
- const u32 element;
-};
-
-/// Constant buffer node, usually mapped to uniform buffers in GLSL
-class CbufNode final {
-public:
- explicit constexpr CbufNode(u32 index, Node offset) : index{index}, offset{offset} {}
-
- u32 GetIndex() const {
- return index;
- }
-
- Node GetOffset() const {
- return offset;
- }
-
-private:
- const u32 index;
- const Node offset;
-};
-
-/// Local memory node
-class LmemNode final {
-public:
- explicit constexpr LmemNode(Node address) : address{address} {}
-
- Node GetAddress() const {
- return address;
- }
-
-private:
- const Node address;
-};
-
-/// Global memory node
-class GmemNode final {
-public:
- explicit constexpr GmemNode(Node real_address, Node base_address,
- const GlobalMemoryBase& descriptor)
- : real_address{real_address}, base_address{base_address}, descriptor{descriptor} {}
-
- Node GetRealAddress() const {
- return real_address;
- }
-
- Node GetBaseAddress() const {
- return base_address;
- }
-
- const GlobalMemoryBase& GetDescriptor() const {
- return descriptor;
- }
-
-private:
- const Node real_address;
- const Node base_address;
- const GlobalMemoryBase descriptor;
-};
-
-/// Commentary, can be dropped
-class CommentNode final {
-public:
- explicit CommentNode(std::string text) : text{std::move(text)} {}
-
- const std::string& GetText() const {
- return text;
- }
-
-private:
- std::string text;
+struct GlobalMemoryUsage {
+ bool is_read{};
+ bool is_written{};
};
class ShaderIR final {
public:
- explicit ShaderIR(const ProgramCode& program_code, u32 main_offset)
- : program_code{program_code}, main_offset{main_offset} {
-
- Decode();
- }
+ explicit ShaderIR(const ProgramCode& program_code, u32 main_offset);
+ ~ShaderIR();
const std::map<u32, NodeBlock>& GetBasicBlocks() const {
return basic_blocks;
@@ -552,8 +88,7 @@ public:
return used_predicates;
}
- const std::map<Tegra::Shader::Attribute::Index, std::set<Tegra::Shader::IpaMode>>&
- GetInputAttributes() const {
+ const std::set<Tegra::Shader::Attribute::Index>& GetInputAttributes() const {
return used_input_attributes;
}
@@ -574,14 +109,18 @@ public:
return used_clip_distances;
}
- const std::set<GlobalMemoryBase>& GetGlobalMemoryBases() const {
- return used_global_memory_bases;
+ const std::map<GlobalMemoryBase, GlobalMemoryUsage>& GetGlobalMemory() const {
+ return used_global_memory;
}
std::size_t GetLength() const {
return static_cast<std::size_t>(coverage_end * sizeof(u64));
}
+ bool HasPhysicalAttributes() const {
+ return uses_physical_attributes;
+ }
+
const Tegra::Shader::Header& GetHeader() const {
return header;
}
@@ -614,6 +153,7 @@ private:
u32 DecodeHfma2(NodeBlock& bb, u32 pc);
u32 DecodeConversion(NodeBlock& bb, u32 pc);
u32 DecodeMemory(NodeBlock& bb, u32 pc);
+ u32 DecodeTexture(NodeBlock& bb, u32 pc);
u32 DecodeFloatSetPredicate(NodeBlock& bb, u32 pc);
u32 DecodeIntegerSetPredicate(NodeBlock& bb, u32 pc);
u32 DecodeHalfSetPredicate(NodeBlock& bb, u32 pc);
@@ -627,26 +167,6 @@ private:
u32 DecodeXmad(NodeBlock& bb, u32 pc);
u32 DecodeOther(NodeBlock& bb, u32 pc);
- /// Internalizes node's data and returns a managed pointer to a clone of that node
- Node StoreNode(NodeData&& node_data);
-
- /// Creates a conditional node
- Node Conditional(Node condition, std::vector<Node>&& code);
- /// Creates a commentary
- Node Comment(const std::string& text);
- /// Creates an u32 immediate
- Node Immediate(u32 value);
- /// Creates a s32 immediate
- Node Immediate(s32 value) {
- return Immediate(static_cast<u32>(value));
- }
- /// Creates a f32 immediate
- Node Immediate(f32 value) {
- u32 integral;
- std::memcpy(&integral, &value, sizeof(u32));
- return Immediate(integral);
- }
-
/// Generates a node for a passed register.
Node GetRegister(Tegra::Shader::Register reg);
/// Generates a node representing a 19-bit immediate value
@@ -662,8 +182,9 @@ private:
/// Generates a predicate node for an immediate true or false value
Node GetPredicate(bool immediate);
/// Generates a node representing an input attribute. Keeps track of used attributes.
- Node GetInputAttribute(Tegra::Shader::Attribute::Index index, u64 element,
- const Tegra::Shader::IpaMode& input_mode, Node buffer = {});
+ Node GetInputAttribute(Tegra::Shader::Attribute::Index index, u64 element, Node buffer = {});
+ /// Generates a node representing a physical input attribute.
+ Node GetPhysicalInputAttribute(Tegra::Shader::Register physical_address, Node buffer = {});
/// Generates a node representing an output attribute. Keeps track of used attributes.
Node GetOutputAttribute(Tegra::Shader::Attribute::Index index, u64 element, Node buffer);
/// Generates a node representing an internal flag
@@ -701,10 +222,14 @@ private:
/// Unpacks a half immediate from an instruction
Node UnpackHalfImmediate(Tegra::Shader::Instruction instr, bool has_negation);
+ /// Unpacks a binary value into a half float pair with a type format
+ Node UnpackHalfFloat(Node value, Tegra::Shader::HalfType type);
/// Merges a half pair into another value
Node HalfMerge(Node dest, Node src, Tegra::Shader::HalfMerge merge);
/// Conditionally absolute/negated half float pair. Absolute is applied first
Node GetOperandAbsNegHalf(Node value, bool absolute, bool negate);
+ /// Conditionally saturates a half float pair
+ Node GetSaturatedHalfFloat(Node value, bool saturate = true);
/// Returns a predicate comparing two floats
Node GetPredicateComparisonFloat(Tegra::Shader::PredCondition condition, Node op_a, Node op_b);
@@ -712,8 +237,7 @@ private:
Node GetPredicateComparisonInteger(Tegra::Shader::PredCondition condition, bool is_signed,
Node op_a, Node op_b);
/// Returns a predicate comparing two half floats. meta consumes how both pairs will be compared
- Node GetPredicateComparisonHalf(Tegra::Shader::PredCondition condition,
- const MetaHalfArithmetic& meta, Node op_a, Node op_b);
+ Node GetPredicateComparisonHalf(Tegra::Shader::PredCondition condition, Node op_a, Node op_b);
/// Returns a predicate combiner operation
OperationCode GetPredicateCombiner(Tegra::Shader::PredOperation operation);
@@ -725,6 +249,11 @@ private:
const Sampler& GetSampler(const Tegra::Shader::Sampler& sampler,
Tegra::Shader::TextureType type, bool is_array, bool is_shadow);
+ // Accesses a texture sampler for a bindless texture.
+ const Sampler& GetBindlessSampler(const Tegra::Shader::Register& reg,
+ Tegra::Shader::TextureType type, bool is_array,
+ bool is_shadow);
+
/// Extracts a sequence of bits from a node
Node BitfieldExtract(Node value, u32 offset, u32 bits);
@@ -738,14 +267,15 @@ private:
Node4 GetTexCode(Tegra::Shader::Instruction instr, Tegra::Shader::TextureType texture_type,
Tegra::Shader::TextureProcessMode process_mode, bool depth_compare,
- bool is_array);
+ bool is_array, bool is_aoffi,
+ std::optional<Tegra::Shader::Register> bindless_reg);
Node4 GetTexsCode(Tegra::Shader::Instruction instr, Tegra::Shader::TextureType texture_type,
Tegra::Shader::TextureProcessMode process_mode, bool depth_compare,
bool is_array);
Node4 GetTld4Code(Tegra::Shader::Instruction instr, Tegra::Shader::TextureType texture_type,
- bool depth_compare, bool is_array);
+ bool depth_compare, bool is_array, bool is_aoffi);
Node4 GetTldsCode(Tegra::Shader::Instruction instr, Tegra::Shader::TextureType texture_type,
bool is_array);
@@ -754,9 +284,12 @@ private:
Tegra::Shader::TextureType texture_type, bool depth_compare, bool is_array,
bool lod_bias_enabled, std::size_t max_coords, std::size_t max_inputs);
+ std::vector<Node> GetAoffiCoordinates(Node aoffi_reg, std::size_t coord_count, bool is_tld4);
+
Node4 GetTextureCode(Tegra::Shader::Instruction instr, Tegra::Shader::TextureType texture_type,
Tegra::Shader::TextureProcessMode process_mode, std::vector<Node> coords,
- Node array, Node depth_compare, u32 bias_offset);
+ Node array, Node depth_compare, u32 bias_offset, std::vector<Node> aoffi,
+ std::optional<Tegra::Shader::Register> bindless_reg);
Node GetVideoOperand(Node op, bool is_chunk, bool is_signed, Tegra::Shader::VideoType type,
u64 byte_height);
@@ -768,42 +301,15 @@ private:
void WriteLop3Instruction(NodeBlock& bb, Tegra::Shader::Register dest, Node op_a, Node op_b,
Node op_c, Node imm_lut, bool sets_cc);
- Node TrackCbuf(Node tracked, const NodeBlock& code, s64 cursor);
-
- std::pair<Node, s64> TrackRegister(const GprNode* tracked, const NodeBlock& code, s64 cursor);
-
- template <typename... T>
- Node Operation(OperationCode code, const T*... operands) {
- return StoreNode(OperationNode(code, operands...));
- }
-
- template <typename... T>
- Node Operation(OperationCode code, Meta&& meta, const T*... operands) {
- return StoreNode(OperationNode(code, std::move(meta), operands...));
- }
-
- template <typename... T>
- Node Operation(OperationCode code, std::vector<Node>&& operands) {
- return StoreNode(OperationNode(code, std::move(operands)));
- }
+ Node TrackCbuf(Node tracked, const NodeBlock& code, s64 cursor) const;
- template <typename... T>
- Node Operation(OperationCode code, Meta&& meta, std::vector<Node>&& operands) {
- return StoreNode(OperationNode(code, std::move(meta), std::move(operands)));
- }
+ std::optional<u32> TrackImmediate(Node tracked, const NodeBlock& code, s64 cursor) const;
- template <typename... T>
- Node SignedOperation(OperationCode code, bool is_signed, const T*... operands) {
- return StoreNode(OperationNode(SignedToUnsignedCode(code, is_signed), operands...));
- }
+ std::pair<Node, s64> TrackRegister(const GprNode* tracked, const NodeBlock& code,
+ s64 cursor) const;
- template <typename... T>
- Node SignedOperation(OperationCode code, bool is_signed, Meta&& meta, const T*... operands) {
- return StoreNode(
- OperationNode(SignedToUnsignedCode(code, is_signed), std::move(meta), operands...));
- }
-
- static OperationCode SignedToUnsignedCode(OperationCode operation_code, bool is_signed);
+ std::tuple<Node, Node, GlobalMemoryBase> TrackAndGetGlobalMemory(
+ NodeBlock& bb, Tegra::Shader::Instruction instr, bool is_write);
const ProgramCode& program_code;
const u32 main_offset;
@@ -815,17 +321,15 @@ private:
std::map<u32, NodeBlock> basic_blocks;
NodeBlock global_code;
- std::vector<std::unique_ptr<NodeData>> stored_nodes;
-
std::set<u32> used_registers;
std::set<Tegra::Shader::Pred> used_predicates;
- std::map<Tegra::Shader::Attribute::Index, std::set<Tegra::Shader::IpaMode>>
- used_input_attributes;
+ 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::set<Sampler> used_samplers;
std::array<bool, Tegra::Engines::Maxwell3D::Regs::NumClipDistances> used_clip_distances{};
- std::set<GlobalMemoryBase> used_global_memory_bases;
+ std::map<GlobalMemoryBase, GlobalMemoryUsage> used_global_memory;
+ bool uses_physical_attributes{}; // Shader uses AL2P or physical attribute read/writes
Tegra::Shader::Header header;
};
diff --git a/src/video_core/shader/track.cpp b/src/video_core/shader/track.cpp
index 33b071747..fc957d980 100644
--- a/src/video_core/shader/track.cpp
+++ b/src/video_core/shader/track.cpp
@@ -6,6 +6,7 @@
#include <utility>
#include <variant>
+#include "common/common_types.h"
#include "video_core/shader/shader_ir.h"
namespace VideoCommon::Shader {
@@ -14,29 +15,31 @@ namespace {
std::pair<Node, s64> FindOperation(const NodeBlock& code, s64 cursor,
OperationCode operation_code) {
for (; cursor >= 0; --cursor) {
- const Node node = code[cursor];
- if (const auto operation = std::get_if<OperationNode>(node)) {
- if (operation->GetCode() == operation_code)
+ const Node node = code.at(cursor);
+ if (const auto operation = std::get_if<OperationNode>(&*node)) {
+ if (operation->GetCode() == operation_code) {
return {node, cursor};
+ }
}
- if (const auto conditional = std::get_if<ConditionalNode>(node)) {
+ if (const auto conditional = std::get_if<ConditionalNode>(&*node)) {
const auto& conditional_code = conditional->GetCode();
const auto [found, internal_cursor] = FindOperation(
conditional_code, static_cast<s64>(conditional_code.size() - 1), operation_code);
- if (found)
+ if (found) {
return {found, cursor};
+ }
}
}
return {};
}
} // namespace
-Node ShaderIR::TrackCbuf(Node tracked, const NodeBlock& code, s64 cursor) {
- if (const auto cbuf = std::get_if<CbufNode>(tracked)) {
+Node ShaderIR::TrackCbuf(Node tracked, const NodeBlock& code, s64 cursor) const {
+ if (const auto cbuf = std::get_if<CbufNode>(&*tracked)) {
// Cbuf found, but it has to be immediate
return std::holds_alternative<ImmediateNode>(*cbuf->GetOffset()) ? tracked : nullptr;
}
- if (const auto gpr = std::get_if<GprNode>(tracked)) {
+ if (const auto gpr = std::get_if<GprNode>(&*tracked)) {
if (gpr->GetIndex() == Tegra::Shader::Register::ZeroIndex) {
return nullptr;
}
@@ -48,7 +51,7 @@ Node ShaderIR::TrackCbuf(Node tracked, const NodeBlock& code, s64 cursor) {
}
return TrackCbuf(source, code, new_cursor);
}
- if (const auto operation = std::get_if<OperationNode>(tracked)) {
+ if (const auto operation = std::get_if<OperationNode>(&*tracked)) {
for (std::size_t i = 0; i < operation->GetOperandsCount(); ++i) {
if (const auto found = TrackCbuf((*operation)[i], code, cursor)) {
// Cbuf found in operand
@@ -57,25 +60,39 @@ Node ShaderIR::TrackCbuf(Node tracked, const NodeBlock& code, s64 cursor) {
}
return nullptr;
}
- if (const auto conditional = std::get_if<ConditionalNode>(tracked)) {
+ if (const auto conditional = std::get_if<ConditionalNode>(&*tracked)) {
const auto& conditional_code = conditional->GetCode();
return TrackCbuf(tracked, conditional_code, static_cast<s64>(conditional_code.size()));
}
return nullptr;
}
+std::optional<u32> ShaderIR::TrackImmediate(Node tracked, const NodeBlock& code, s64 cursor) const {
+ // Reduce the cursor in one to avoid infinite loops when the instruction sets the same register
+ // that it uses as operand
+ const auto [found, found_cursor] =
+ TrackRegister(&std::get<GprNode>(*tracked), code, cursor - 1);
+ if (!found) {
+ return {};
+ }
+ if (const auto immediate = std::get_if<ImmediateNode>(&*found)) {
+ return immediate->GetValue();
+ }
+ return {};
+}
+
std::pair<Node, s64> ShaderIR::TrackRegister(const GprNode* tracked, const NodeBlock& code,
- s64 cursor) {
+ s64 cursor) const {
for (; cursor >= 0; --cursor) {
const auto [found_node, new_cursor] = FindOperation(code, cursor, OperationCode::Assign);
if (!found_node) {
return {};
}
- const auto operation = std::get_if<OperationNode>(found_node);
+ const auto operation = std::get_if<OperationNode>(&*found_node);
ASSERT(operation);
const auto& target = (*operation)[0];
- if (const auto gpr_target = std::get_if<GprNode>(target)) {
+ if (const auto gpr_target = std::get_if<GprNode>(&*target)) {
if (gpr_target->GetIndex() == tracked->GetIndex()) {
return {(*operation)[1], new_cursor};
}
diff --git a/src/video_core/surface.cpp b/src/video_core/surface.cpp
index a7ac26d71..6384fa8d2 100644
--- a/src/video_core/surface.cpp
+++ b/src/video_core/surface.cpp
@@ -178,39 +178,44 @@ PixelFormat PixelFormatFromTextureFormat(Tegra::Texture::TextureFormat format,
return PixelFormat::ABGR8S;
case Tegra::Texture::ComponentType::UINT:
return PixelFormat::ABGR8UI;
+ default:
+ break;
}
- LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", static_cast<u32>(component_type));
- UNREACHABLE();
+ break;
case Tegra::Texture::TextureFormat::B5G6R5:
switch (component_type) {
case Tegra::Texture::ComponentType::UNORM:
return PixelFormat::B5G6R5U;
+ default:
+ break;
}
- LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", static_cast<u32>(component_type));
- UNREACHABLE();
+ break;
case Tegra::Texture::TextureFormat::A2B10G10R10:
switch (component_type) {
case Tegra::Texture::ComponentType::UNORM:
return PixelFormat::A2B10G10R10U;
+ default:
+ break;
}
- LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", static_cast<u32>(component_type));
- UNREACHABLE();
+ break;
case Tegra::Texture::TextureFormat::A1B5G5R5:
switch (component_type) {
case Tegra::Texture::ComponentType::UNORM:
return PixelFormat::A1B5G5R5U;
+ default:
+ break;
}
- LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", static_cast<u32>(component_type));
- UNREACHABLE();
+ break;
case Tegra::Texture::TextureFormat::R8:
switch (component_type) {
case Tegra::Texture::ComponentType::UNORM:
return PixelFormat::R8U;
case Tegra::Texture::ComponentType::UINT:
return PixelFormat::R8UI;
+ default:
+ break;
}
- LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", static_cast<u32>(component_type));
- UNREACHABLE();
+ break;
case Tegra::Texture::TextureFormat::G8R8:
// TextureFormat::G8R8 is actually ordered red then green, as such we can use
// PixelFormat::RG8U and PixelFormat::RG8S. This was tested with The Legend of Zelda: Breath
@@ -220,50 +225,55 @@ PixelFormat PixelFormatFromTextureFormat(Tegra::Texture::TextureFormat format,
return PixelFormat::RG8U;
case Tegra::Texture::ComponentType::SNORM:
return PixelFormat::RG8S;
+ default:
+ break;
}
- LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", static_cast<u32>(component_type));
- UNREACHABLE();
+ break;
case Tegra::Texture::TextureFormat::R16_G16_B16_A16:
switch (component_type) {
case Tegra::Texture::ComponentType::UNORM:
return PixelFormat::RGBA16U;
case Tegra::Texture::ComponentType::FLOAT:
return PixelFormat::RGBA16F;
+ default:
+ break;
}
- LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", static_cast<u32>(component_type));
- UNREACHABLE();
+ break;
case Tegra::Texture::TextureFormat::BF10GF11RF11:
switch (component_type) {
case Tegra::Texture::ComponentType::FLOAT:
return PixelFormat::R11FG11FB10F;
+ default:
+ break;
}
- LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", static_cast<u32>(component_type));
- UNREACHABLE();
case Tegra::Texture::TextureFormat::R32_G32_B32_A32:
switch (component_type) {
case Tegra::Texture::ComponentType::FLOAT:
return PixelFormat::RGBA32F;
case Tegra::Texture::ComponentType::UINT:
return PixelFormat::RGBA32UI;
+ default:
+ break;
}
- LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", static_cast<u32>(component_type));
- UNREACHABLE();
+ break;
case Tegra::Texture::TextureFormat::R32_G32:
switch (component_type) {
case Tegra::Texture::ComponentType::FLOAT:
return PixelFormat::RG32F;
case Tegra::Texture::ComponentType::UINT:
return PixelFormat::RG32UI;
+ default:
+ break;
}
- LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", static_cast<u32>(component_type));
- UNREACHABLE();
+ break;
case Tegra::Texture::TextureFormat::R32_G32_B32:
switch (component_type) {
case Tegra::Texture::ComponentType::FLOAT:
return PixelFormat::RGB32F;
+ default:
+ break;
}
- LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", static_cast<u32>(component_type));
- UNREACHABLE();
+ break;
case Tegra::Texture::TextureFormat::R16:
switch (component_type) {
case Tegra::Texture::ComponentType::FLOAT:
@@ -276,24 +286,28 @@ PixelFormat PixelFormatFromTextureFormat(Tegra::Texture::TextureFormat format,
return PixelFormat::R16UI;
case Tegra::Texture::ComponentType::SINT:
return PixelFormat::R16I;
+ default:
+ break;
}
- LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", static_cast<u32>(component_type));
- UNREACHABLE();
+ break;
case Tegra::Texture::TextureFormat::R32:
switch (component_type) {
case Tegra::Texture::ComponentType::FLOAT:
return PixelFormat::R32F;
case Tegra::Texture::ComponentType::UINT:
return PixelFormat::R32UI;
+ default:
+ break;
}
- LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", static_cast<u32>(component_type));
- UNREACHABLE();
+ break;
case Tegra::Texture::TextureFormat::ZF32:
return PixelFormat::Z32F;
case Tegra::Texture::TextureFormat::Z16:
return PixelFormat::Z16;
case Tegra::Texture::TextureFormat::Z24S8:
return PixelFormat::Z24S8;
+ case Tegra::Texture::TextureFormat::ZF32_X24S8:
+ return PixelFormat::Z32FS8;
case Tegra::Texture::TextureFormat::DXT1:
return is_srgb ? PixelFormat::DXT1_SRGB : PixelFormat::DXT1;
case Tegra::Texture::TextureFormat::DXT23:
@@ -308,9 +322,10 @@ PixelFormat PixelFormatFromTextureFormat(Tegra::Texture::TextureFormat format,
return PixelFormat::DXN2UNORM;
case Tegra::Texture::ComponentType::SNORM:
return PixelFormat::DXN2SNORM;
+ default:
+ break;
}
- LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", static_cast<u32>(component_type));
- UNREACHABLE();
+ break;
case Tegra::Texture::TextureFormat::BC7U:
return is_srgb ? PixelFormat::BC7U_SRGB : PixelFormat::BC7U;
case Tegra::Texture::TextureFormat::BC6H_UF16:
@@ -341,15 +356,17 @@ PixelFormat PixelFormatFromTextureFormat(Tegra::Texture::TextureFormat format,
return PixelFormat::RG16UI;
case Tegra::Texture::ComponentType::SINT:
return PixelFormat::RG16I;
+ default:
+ break;
}
- LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", static_cast<u32>(component_type));
- UNREACHABLE();
+ break;
default:
- LOG_CRITICAL(HW_GPU, "Unimplemented format={}, component_type={}", static_cast<u32>(format),
- static_cast<u32>(component_type));
- UNREACHABLE();
- return PixelFormat::ABGR8U;
+ break;
}
+ LOG_CRITICAL(HW_GPU, "Unimplemented format={}, component_type={}", static_cast<u32>(format),
+ static_cast<u32>(component_type));
+ UNREACHABLE();
+ return PixelFormat::ABGR8U;
}
ComponentType ComponentTypeFromTexture(Tegra::Texture::ComponentType type) {
@@ -511,8 +528,9 @@ bool IsFormatBCn(PixelFormat format) {
case PixelFormat::DXT45_SRGB:
case PixelFormat::BC7U_SRGB:
return true;
+ default:
+ return false;
}
- return false;
}
} // namespace VideoCore::Surface
diff --git a/src/video_core/texture_cache.cpp b/src/video_core/texture_cache.cpp
new file mode 100644
index 000000000..e96eba7cc
--- /dev/null
+++ b/src/video_core/texture_cache.cpp
@@ -0,0 +1,386 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/alignment.h"
+#include "common/assert.h"
+#include "common/cityhash.h"
+#include "common/common_types.h"
+#include "core/core.h"
+#include "video_core/surface.h"
+#include "video_core/texture_cache.h"
+#include "video_core/textures/decoders.h"
+#include "video_core/textures/texture.h"
+
+namespace VideoCommon {
+
+using VideoCore::Surface::SurfaceTarget;
+
+using VideoCore::Surface::ComponentTypeFromDepthFormat;
+using VideoCore::Surface::ComponentTypeFromRenderTarget;
+using VideoCore::Surface::ComponentTypeFromTexture;
+using VideoCore::Surface::PixelFormatFromDepthFormat;
+using VideoCore::Surface::PixelFormatFromRenderTargetFormat;
+using VideoCore::Surface::PixelFormatFromTextureFormat;
+using VideoCore::Surface::SurfaceTargetFromTextureType;
+
+constexpr u32 GetMipmapSize(bool uncompressed, u32 mip_size, u32 tile) {
+ return uncompressed ? mip_size : std::max(1U, (mip_size + tile - 1) / tile);
+}
+
+SurfaceParams SurfaceParams::CreateForTexture(Core::System& system,
+ const Tegra::Texture::FullTextureInfo& config) {
+ SurfaceParams params;
+ params.is_tiled = config.tic.IsTiled();
+ params.block_width = params.is_tiled ? config.tic.BlockWidth() : 0,
+ params.block_height = params.is_tiled ? config.tic.BlockHeight() : 0,
+ params.block_depth = params.is_tiled ? config.tic.BlockDepth() : 0,
+ params.tile_width_spacing = params.is_tiled ? (1 << config.tic.tile_width_spacing.Value()) : 1;
+ params.pixel_format =
+ PixelFormatFromTextureFormat(config.tic.format, config.tic.r_type.Value(), false);
+ params.component_type = ComponentTypeFromTexture(config.tic.r_type.Value());
+ params.type = GetFormatType(params.pixel_format);
+ params.target = SurfaceTargetFromTextureType(config.tic.texture_type);
+ params.width = Common::AlignUp(config.tic.Width(), GetCompressionFactor(params.pixel_format));
+ params.height = Common::AlignUp(config.tic.Height(), GetCompressionFactor(params.pixel_format));
+ params.depth = config.tic.Depth();
+ if (params.target == SurfaceTarget::TextureCubemap ||
+ params.target == SurfaceTarget::TextureCubeArray) {
+ params.depth *= 6;
+ }
+ params.pitch = params.is_tiled ? 0 : config.tic.Pitch();
+ params.unaligned_height = config.tic.Height();
+ params.num_levels = config.tic.max_mip_level + 1;
+
+ params.CalculateCachedValues();
+ return params;
+}
+
+SurfaceParams SurfaceParams::CreateForDepthBuffer(
+ Core::System& system, u32 zeta_width, u32 zeta_height, Tegra::DepthFormat format,
+ u32 block_width, u32 block_height, u32 block_depth,
+ Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout type) {
+ SurfaceParams params;
+ params.is_tiled = type == Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear;
+ params.block_width = 1 << std::min(block_width, 5U);
+ params.block_height = 1 << std::min(block_height, 5U);
+ params.block_depth = 1 << std::min(block_depth, 5U);
+ params.tile_width_spacing = 1;
+ params.pixel_format = PixelFormatFromDepthFormat(format);
+ params.component_type = ComponentTypeFromDepthFormat(format);
+ params.type = GetFormatType(params.pixel_format);
+ params.width = zeta_width;
+ params.height = zeta_height;
+ params.unaligned_height = zeta_height;
+ params.target = SurfaceTarget::Texture2D;
+ params.depth = 1;
+ params.num_levels = 1;
+
+ params.CalculateCachedValues();
+ return params;
+}
+
+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.block_width = 1 << config.memory_layout.block_width;
+ params.block_height = 1 << config.memory_layout.block_height;
+ params.block_depth = 1 << config.memory_layout.block_depth;
+ params.tile_width_spacing = 1;
+ params.pixel_format = PixelFormatFromRenderTargetFormat(config.format);
+ params.component_type = ComponentTypeFromRenderTarget(config.format);
+ params.type = GetFormatType(params.pixel_format);
+ if (params.is_tiled) {
+ 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.depth = 1;
+ params.unaligned_height = config.height;
+ params.target = SurfaceTarget::Texture2D;
+ params.num_levels = 1;
+
+ params.CalculateCachedValues();
+ return params;
+}
+
+SurfaceParams SurfaceParams::CreateForFermiCopySurface(
+ const Tegra::Engines::Fermi2D::Regs::Surface& config) {
+ SurfaceParams params{};
+ params.is_tiled = !config.linear;
+ params.block_width = params.is_tiled ? std::min(config.BlockWidth(), 32U) : 0,
+ params.block_height = params.is_tiled ? std::min(config.BlockHeight(), 32U) : 0,
+ params.block_depth = params.is_tiled ? std::min(config.BlockDepth(), 32U) : 0,
+ params.tile_width_spacing = 1;
+ params.pixel_format = PixelFormatFromRenderTargetFormat(config.format);
+ params.component_type = ComponentTypeFromRenderTarget(config.format);
+ params.type = GetFormatType(params.pixel_format);
+ params.width = config.width;
+ params.height = config.height;
+ params.unaligned_height = config.height;
+ // TODO(Rodrigo): Try to guess the surface target from depth and layer parameters
+ params.target = SurfaceTarget::Texture2D;
+ params.depth = 1;
+ params.num_levels = 1;
+
+ params.CalculateCachedValues();
+ return params;
+}
+
+u32 SurfaceParams::GetMipWidth(u32 level) const {
+ return std::max(1U, width >> level);
+}
+
+u32 SurfaceParams::GetMipHeight(u32 level) const {
+ return std::max(1U, height >> level);
+}
+
+u32 SurfaceParams::GetMipDepth(u32 level) const {
+ return IsLayered() ? depth : std::max(1U, depth >> level);
+}
+
+bool SurfaceParams::IsLayered() const {
+ switch (target) {
+ case SurfaceTarget::Texture1DArray:
+ case SurfaceTarget::Texture2DArray:
+ case SurfaceTarget::TextureCubeArray:
+ case SurfaceTarget::TextureCubemap:
+ return true;
+ default:
+ return false;
+ }
+}
+
+u32 SurfaceParams::GetMipBlockHeight(u32 level) const {
+ // Auto block resizing algorithm from:
+ // https://cgit.freedesktop.org/mesa/mesa/tree/src/gallium/drivers/nouveau/nv50/nv50_miptree.c
+ if (level == 0) {
+ return block_height;
+ }
+ const u32 height{GetMipHeight(level)};
+ const u32 default_block_height{GetDefaultBlockHeight(pixel_format)};
+ const u32 blocks_in_y{(height + default_block_height - 1) / default_block_height};
+ u32 block_height = 16;
+ while (block_height > 1 && blocks_in_y <= block_height * 4) {
+ block_height >>= 1;
+ }
+ return block_height;
+}
+
+u32 SurfaceParams::GetMipBlockDepth(u32 level) const {
+ if (level == 0)
+ return block_depth;
+ if (target != SurfaceTarget::Texture3D)
+ return 1;
+
+ const u32 depth{GetMipDepth(level)};
+ u32 block_depth = 32;
+ while (block_depth > 1 && depth * 2 <= block_depth) {
+ block_depth >>= 1;
+ }
+ if (block_depth == 32 && GetMipBlockHeight(level) >= 4) {
+ return 16;
+ }
+ return block_depth;
+}
+
+std::size_t SurfaceParams::GetGuestMipmapLevelOffset(u32 level) const {
+ std::size_t offset = 0;
+ for (u32 i = 0; i < level; i++) {
+ offset += GetInnerMipmapMemorySize(i, false, IsLayered(), false);
+ }
+ return offset;
+}
+
+std::size_t SurfaceParams::GetHostMipmapLevelOffset(u32 level) const {
+ std::size_t offset = 0;
+ for (u32 i = 0; i < level; i++) {
+ offset += GetInnerMipmapMemorySize(i, true, false, false);
+ }
+ return offset;
+}
+
+std::size_t SurfaceParams::GetGuestLayerSize() const {
+ return GetInnerMemorySize(false, true, false);
+}
+
+std::size_t SurfaceParams::GetHostLayerSize(u32 level) const {
+ return GetInnerMipmapMemorySize(level, true, IsLayered(), false);
+}
+
+bool SurfaceParams::IsFamiliar(const SurfaceParams& view_params) const {
+ if (std::tie(is_tiled, tile_width_spacing, pixel_format, component_type, type) !=
+ std::tie(view_params.is_tiled, view_params.tile_width_spacing, view_params.pixel_format,
+ view_params.component_type, view_params.type)) {
+ return false;
+ }
+
+ const SurfaceTarget view_target{view_params.target};
+ if (view_target == target) {
+ return true;
+ }
+
+ switch (target) {
+ case SurfaceTarget::Texture1D:
+ case SurfaceTarget::Texture2D:
+ case SurfaceTarget::Texture3D:
+ return false;
+ case SurfaceTarget::Texture1DArray:
+ return view_target == SurfaceTarget::Texture1D;
+ case SurfaceTarget::Texture2DArray:
+ return view_target == SurfaceTarget::Texture2D;
+ case SurfaceTarget::TextureCubemap:
+ return view_target == SurfaceTarget::Texture2D ||
+ view_target == SurfaceTarget::Texture2DArray;
+ case SurfaceTarget::TextureCubeArray:
+ return view_target == SurfaceTarget::Texture2D ||
+ view_target == SurfaceTarget::Texture2DArray ||
+ view_target == SurfaceTarget::TextureCubemap;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented texture family={}", static_cast<u32>(target));
+ return false;
+ }
+}
+
+bool SurfaceParams::IsPixelFormatZeta() const {
+ return pixel_format >= VideoCore::Surface::PixelFormat::MaxColorFormat &&
+ pixel_format < VideoCore::Surface::PixelFormat::MaxDepthStencilFormat;
+}
+
+void SurfaceParams::CalculateCachedValues() {
+ guest_size_in_bytes = GetInnerMemorySize(false, false, false);
+
+ // ASTC is uncompressed in software, in emulated as RGBA8
+ if (IsPixelFormatASTC(pixel_format)) {
+ host_size_in_bytes = width * height * depth * 4;
+ } else {
+ host_size_in_bytes = GetInnerMemorySize(true, false, false);
+ }
+
+ switch (target) {
+ case SurfaceTarget::Texture1D:
+ case SurfaceTarget::Texture2D:
+ case SurfaceTarget::Texture3D:
+ num_layers = 1;
+ break;
+ case SurfaceTarget::Texture1DArray:
+ case SurfaceTarget::Texture2DArray:
+ case SurfaceTarget::TextureCubemap:
+ case SurfaceTarget::TextureCubeArray:
+ num_layers = depth;
+ break;
+ default:
+ UNREACHABLE();
+ }
+}
+
+std::size_t SurfaceParams::GetInnerMipmapMemorySize(u32 level, bool as_host_size, bool layer_only,
+ bool uncompressed) const {
+ const bool tiled{as_host_size ? false : is_tiled};
+ const u32 tile_x{GetDefaultBlockWidth(pixel_format)};
+ const u32 tile_y{GetDefaultBlockHeight(pixel_format)};
+ const u32 width{GetMipmapSize(uncompressed, GetMipWidth(level), tile_x)};
+ const u32 height{GetMipmapSize(uncompressed, GetMipHeight(level), tile_y)};
+ const u32 depth{layer_only ? 1U : GetMipDepth(level)};
+ return Tegra::Texture::CalculateSize(tiled, GetBytesPerPixel(pixel_format), width, height,
+ depth, GetMipBlockHeight(level), GetMipBlockDepth(level));
+}
+
+std::size_t SurfaceParams::GetInnerMemorySize(bool as_host_size, bool layer_only,
+ bool uncompressed) const {
+ std::size_t size = 0;
+ for (u32 level = 0; level < num_levels; ++level) {
+ size += GetInnerMipmapMemorySize(level, as_host_size, layer_only, uncompressed);
+ }
+ if (!as_host_size && is_tiled) {
+ size = Common::AlignUp(size, Tegra::Texture::GetGOBSize() * block_height * block_depth);
+ }
+ return size;
+}
+
+std::map<u64, std::pair<u32, u32>> SurfaceParams::CreateViewOffsetMap() const {
+ std::map<u64, std::pair<u32, u32>> view_offset_map;
+ switch (target) {
+ case SurfaceTarget::Texture1D:
+ case SurfaceTarget::Texture2D:
+ case SurfaceTarget::Texture3D: {
+ constexpr u32 layer = 0;
+ for (u32 level = 0; level < num_levels; ++level) {
+ const std::size_t offset{GetGuestMipmapLevelOffset(level)};
+ view_offset_map.insert({offset, {layer, level}});
+ }
+ break;
+ }
+ case SurfaceTarget::Texture1DArray:
+ case SurfaceTarget::Texture2DArray:
+ case SurfaceTarget::TextureCubemap:
+ case SurfaceTarget::TextureCubeArray: {
+ const std::size_t layer_size{GetGuestLayerSize()};
+ for (u32 level = 0; level < num_levels; ++level) {
+ const std::size_t level_offset{GetGuestMipmapLevelOffset(level)};
+ for (u32 layer = 0; layer < num_layers; ++layer) {
+ const auto layer_offset{static_cast<std::size_t>(layer_size * layer)};
+ const std::size_t offset{level_offset + layer_offset};
+ view_offset_map.insert({offset, {layer, level}});
+ }
+ }
+ break;
+ }
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented surface target {}", static_cast<u32>(target));
+ }
+ return view_offset_map;
+}
+
+bool SurfaceParams::IsViewValid(const SurfaceParams& view_params, u32 layer, u32 level) const {
+ return IsDimensionValid(view_params, level) && IsDepthValid(view_params, level) &&
+ IsInBounds(view_params, layer, level);
+}
+
+bool SurfaceParams::IsDimensionValid(const SurfaceParams& view_params, u32 level) const {
+ return view_params.width == GetMipWidth(level) && view_params.height == GetMipHeight(level);
+}
+
+bool SurfaceParams::IsDepthValid(const SurfaceParams& view_params, u32 level) const {
+ if (view_params.target != SurfaceTarget::Texture3D) {
+ return true;
+ }
+ return view_params.depth == GetMipDepth(level);
+}
+
+bool SurfaceParams::IsInBounds(const SurfaceParams& view_params, u32 layer, u32 level) const {
+ return layer + view_params.num_layers <= num_layers &&
+ level + view_params.num_levels <= num_levels;
+}
+
+std::size_t HasheableSurfaceParams::Hash() const {
+ return static_cast<std::size_t>(
+ Common::CityHash64(reinterpret_cast<const char*>(this), sizeof(*this)));
+}
+
+bool HasheableSurfaceParams::operator==(const HasheableSurfaceParams& rhs) const {
+ return std::tie(is_tiled, block_width, block_height, block_depth, tile_width_spacing, width,
+ height, depth, pitch, unaligned_height, num_levels, pixel_format,
+ component_type, 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.unaligned_height, rhs.num_levels, rhs.pixel_format, rhs.component_type,
+ rhs.type, rhs.target);
+}
+
+std::size_t ViewKey::Hash() const {
+ return static_cast<std::size_t>(
+ Common::CityHash64(reinterpret_cast<const char*>(this), sizeof(*this)));
+}
+
+bool ViewKey::operator==(const ViewKey& rhs) const {
+ return std::tie(base_layer, num_layers, base_level, num_levels) ==
+ std::tie(rhs.base_layer, rhs.num_layers, rhs.base_level, rhs.num_levels);
+}
+
+} // namespace VideoCommon
diff --git a/src/video_core/texture_cache.h b/src/video_core/texture_cache.h
new file mode 100644
index 000000000..041551691
--- /dev/null
+++ b/src/video_core/texture_cache.h
@@ -0,0 +1,586 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <list>
+#include <memory>
+#include <set>
+#include <tuple>
+#include <type_traits>
+#include <unordered_map>
+
+#include <boost/icl/interval_map.hpp>
+#include <boost/range/iterator_range.hpp>
+
+#include "common/assert.h"
+#include "common/common_types.h"
+#include "core/memory.h"
+#include "video_core/engines/fermi_2d.h"
+#include "video_core/engines/maxwell_3d.h"
+#include "video_core/gpu.h"
+#include "video_core/rasterizer_interface.h"
+#include "video_core/surface.h"
+
+namespace Core {
+class System;
+}
+
+namespace Tegra::Texture {
+struct FullTextureInfo;
+}
+
+namespace VideoCore {
+class RasterizerInterface;
+}
+
+namespace VideoCommon {
+
+class HasheableSurfaceParams {
+public:
+ std::size_t Hash() const;
+
+ bool operator==(const HasheableSurfaceParams& rhs) const;
+
+protected:
+ // Avoid creation outside of a managed environment.
+ HasheableSurfaceParams() = default;
+
+ bool is_tiled;
+ u32 block_width;
+ u32 block_height;
+ u32 block_depth;
+ u32 tile_width_spacing;
+ u32 width;
+ u32 height;
+ u32 depth;
+ u32 pitch;
+ u32 unaligned_height;
+ u32 num_levels;
+ VideoCore::Surface::PixelFormat pixel_format;
+ VideoCore::Surface::ComponentType component_type;
+ VideoCore::Surface::SurfaceType type;
+ VideoCore::Surface::SurfaceTarget target;
+};
+
+class SurfaceParams final : public HasheableSurfaceParams {
+public:
+ /// Creates SurfaceCachedParams from a texture configuration.
+ static SurfaceParams CreateForTexture(Core::System& system,
+ const Tegra::Texture::FullTextureInfo& config);
+
+ /// Creates SurfaceCachedParams for a depth buffer configuration.
+ static SurfaceParams CreateForDepthBuffer(
+ Core::System& system, u32 zeta_width, u32 zeta_height, Tegra::DepthFormat format,
+ u32 block_width, u32 block_height, u32 block_depth,
+ Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout type);
+
+ /// 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);
+
+ bool IsTiled() const {
+ return is_tiled;
+ }
+
+ u32 GetBlockWidth() const {
+ return block_width;
+ }
+
+ u32 GetTileWidthSpacing() const {
+ return tile_width_spacing;
+ }
+
+ u32 GetWidth() const {
+ return width;
+ }
+
+ u32 GetHeight() const {
+ return height;
+ }
+
+ u32 GetDepth() const {
+ return depth;
+ }
+
+ u32 GetPitch() const {
+ return pitch;
+ }
+
+ u32 GetNumLevels() const {
+ return num_levels;
+ }
+
+ VideoCore::Surface::PixelFormat GetPixelFormat() const {
+ return pixel_format;
+ }
+
+ VideoCore::Surface::ComponentType GetComponentType() const {
+ return component_type;
+ }
+
+ VideoCore::Surface::SurfaceTarget GetTarget() const {
+ return target;
+ }
+
+ VideoCore::Surface::SurfaceType GetType() const {
+ return type;
+ }
+
+ std::size_t GetGuestSizeInBytes() const {
+ return guest_size_in_bytes;
+ }
+
+ std::size_t GetHostSizeInBytes() const {
+ return host_size_in_bytes;
+ }
+
+ u32 GetNumLayers() const {
+ return num_layers;
+ }
+
+ /// Returns the width of a given mipmap level.
+ u32 GetMipWidth(u32 level) const;
+
+ /// Returns the height of a given mipmap level.
+ u32 GetMipHeight(u32 level) const;
+
+ /// Returns the depth of a given mipmap level.
+ u32 GetMipDepth(u32 level) const;
+
+ /// Returns true if these parameters are from a layered surface.
+ bool IsLayered() const;
+
+ /// 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 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) const;
+
+ /// Returns the size of a layer in bytes in guest memory.
+ std::size_t GetGuestLayerSize() const;
+
+ /// Returns the size of a layer in bytes in host memory for a given mipmap level.
+ std::size_t GetHostLayerSize(u32 level) const;
+
+ /// Returns true if another surface can be familiar with this. This is a loosely defined term
+ /// that reflects the possibility of these two surface parameters potentially being part of a
+ /// bigger superset.
+ bool IsFamiliar(const SurfaceParams& view_params) const;
+
+ /// Returns true if the pixel format is a depth and/or stencil format.
+ bool IsPixelFormatZeta() const;
+
+ /// Creates a map that redirects an address difference to a layer and mipmap level.
+ std::map<u64, std::pair<u32, u32>> CreateViewOffsetMap() const;
+
+ /// Returns true if the passed surface view parameters is equal or a valid subset of this.
+ bool IsViewValid(const SurfaceParams& view_params, u32 layer, u32 level) const;
+
+private:
+ /// Calculates values that can be deduced from HasheableSurfaceParams.
+ void CalculateCachedValues();
+
+ /// Returns the size of a given mipmap level.
+ std::size_t GetInnerMipmapMemorySize(u32 level, bool as_host_size, bool layer_only,
+ 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;
+
+ /// Returns true if the passed view width and height match the size of this params in a given
+ /// mipmap level.
+ bool IsDimensionValid(const SurfaceParams& view_params, u32 level) const;
+
+ /// Returns true if the passed view depth match the size of this params in a given mipmap level.
+ bool IsDepthValid(const SurfaceParams& view_params, u32 level) const;
+
+ /// Returns true if the passed view layers and mipmap levels are in bounds.
+ bool IsInBounds(const SurfaceParams& view_params, u32 layer, u32 level) const;
+
+ std::size_t guest_size_in_bytes;
+ std::size_t host_size_in_bytes;
+ u32 num_layers;
+};
+
+struct ViewKey {
+ std::size_t Hash() const;
+
+ bool operator==(const ViewKey& rhs) const;
+
+ u32 base_layer{};
+ u32 num_layers{};
+ u32 base_level{};
+ u32 num_levels{};
+};
+
+} // namespace VideoCommon
+
+namespace std {
+
+template <>
+struct hash<VideoCommon::SurfaceParams> {
+ std::size_t operator()(const VideoCommon::SurfaceParams& k) const noexcept {
+ return k.Hash();
+ }
+};
+
+template <>
+struct hash<VideoCommon::ViewKey> {
+ std::size_t operator()(const VideoCommon::ViewKey& k) const noexcept {
+ return k.Hash();
+ }
+};
+
+} // namespace std
+
+namespace VideoCommon {
+
+template <typename TView, typename TExecutionContext>
+class SurfaceBase {
+ static_assert(std::is_trivially_copyable_v<TExecutionContext>);
+
+public:
+ virtual void LoadBuffer() = 0;
+
+ virtual TExecutionContext FlushBuffer(TExecutionContext exctx) = 0;
+
+ virtual TExecutionContext UploadTexture(TExecutionContext exctx) = 0;
+
+ TView* TryGetView(VAddr view_addr, const SurfaceParams& view_params) {
+ if (view_addr < cpu_addr || !params.IsFamiliar(view_params)) {
+ // It can't be a view if it's in a prior address.
+ return {};
+ }
+
+ const auto relative_offset{static_cast<u64>(view_addr - cpu_addr)};
+ const auto it{view_offset_map.find(relative_offset)};
+ if (it == view_offset_map.end()) {
+ // Couldn't find an aligned view.
+ return {};
+ }
+ const auto [layer, level] = it->second;
+
+ if (!params.IsViewValid(view_params, layer, level)) {
+ return {};
+ }
+
+ return GetView(layer, view_params.GetNumLayers(), level, view_params.GetNumLevels());
+ }
+
+ VAddr GetCpuAddr() const {
+ ASSERT(is_registered);
+ return cpu_addr;
+ }
+
+ u8* GetHostPtr() const {
+ ASSERT(is_registered);
+ return host_ptr;
+ }
+
+ CacheAddr GetCacheAddr() const {
+ ASSERT(is_registered);
+ return cache_addr;
+ }
+
+ std::size_t GetSizeInBytes() const {
+ return params.GetGuestSizeInBytes();
+ }
+
+ void MarkAsModified(bool is_modified_) {
+ is_modified = is_modified_;
+ }
+
+ const SurfaceParams& GetSurfaceParams() const {
+ return params;
+ }
+
+ TView* GetView(VAddr view_addr, const SurfaceParams& view_params) {
+ TView* view{TryGetView(view_addr, view_params)};
+ ASSERT(view != nullptr);
+ return view;
+ }
+
+ void Register(VAddr cpu_addr_, u8* host_ptr_) {
+ ASSERT(!is_registered);
+ is_registered = true;
+ cpu_addr = cpu_addr_;
+ host_ptr = host_ptr_;
+ cache_addr = ToCacheAddr(host_ptr_);
+ }
+
+ void Register(VAddr cpu_addr_) {
+ Register(cpu_addr_, Memory::GetPointer(cpu_addr_));
+ }
+
+ void Unregister() {
+ ASSERT(is_registered);
+ is_registered = false;
+ }
+
+ bool IsRegistered() const {
+ return is_registered;
+ }
+
+protected:
+ explicit SurfaceBase(const SurfaceParams& params)
+ : params{params}, view_offset_map{params.CreateViewOffsetMap()} {}
+
+ ~SurfaceBase() = default;
+
+ virtual std::unique_ptr<TView> CreateView(const ViewKey& view_key) = 0;
+
+ bool IsModified() const {
+ return is_modified;
+ }
+
+ const SurfaceParams params;
+
+private:
+ TView* GetView(u32 base_layer, u32 num_layers, u32 base_level, u32 num_levels) {
+ const ViewKey key{base_layer, num_layers, base_level, num_levels};
+ const auto [entry, is_cache_miss] = views.try_emplace(key);
+ auto& view{entry->second};
+ if (is_cache_miss) {
+ view = CreateView(key);
+ }
+ return view.get();
+ }
+
+ const std::map<u64, std::pair<u32, u32>> view_offset_map;
+
+ VAddr cpu_addr{};
+ u8* host_ptr{};
+ CacheAddr cache_addr{};
+ bool is_modified{};
+ bool is_registered{};
+ std::unordered_map<ViewKey, std::unique_ptr<TView>> views;
+};
+
+template <typename TSurface, typename TView, typename TExecutionContext>
+class TextureCache {
+ static_assert(std::is_trivially_copyable_v<TExecutionContext>);
+ using ResultType = std::tuple<TView*, TExecutionContext>;
+ using IntervalMap = boost::icl::interval_map<CacheAddr, std::set<TSurface*>>;
+ using IntervalType = typename IntervalMap::interval_type;
+
+public:
+ void InvalidateRegion(CacheAddr addr, std::size_t size) {
+ for (TSurface* surface : GetSurfacesInRegion(addr, size)) {
+ if (!surface->IsRegistered()) {
+ // Skip duplicates
+ continue;
+ }
+ Unregister(surface);
+ }
+ }
+
+ ResultType GetTextureSurface(TExecutionContext exctx,
+ const Tegra::Texture::FullTextureInfo& config) {
+ auto& memory_manager{system.GPU().MemoryManager()};
+ const auto cpu_addr{memory_manager.GpuToCpuAddress(config.tic.Address())};
+ if (!cpu_addr) {
+ return {{}, exctx};
+ }
+ const auto params{SurfaceParams::CreateForTexture(system, config)};
+ return GetSurfaceView(exctx, *cpu_addr, params, true);
+ }
+
+ ResultType GetDepthBufferSurface(TExecutionContext exctx, bool preserve_contents) {
+ const auto& regs{system.GPU().Maxwell3D().regs};
+ if (!regs.zeta.Address() || !regs.zeta_enable) {
+ return {{}, exctx};
+ }
+
+ auto& memory_manager{system.GPU().MemoryManager()};
+ const auto cpu_addr{memory_manager.GpuToCpuAddress(regs.zeta.Address())};
+ if (!cpu_addr) {
+ return {{}, exctx};
+ }
+
+ const auto depth_params{SurfaceParams::CreateForDepthBuffer(
+ system, regs.zeta_width, regs.zeta_height, regs.zeta.format,
+ regs.zeta.memory_layout.block_width, regs.zeta.memory_layout.block_height,
+ regs.zeta.memory_layout.block_depth, regs.zeta.memory_layout.type)};
+ return GetSurfaceView(exctx, *cpu_addr, depth_params, preserve_contents);
+ }
+
+ ResultType GetColorBufferSurface(TExecutionContext exctx, std::size_t index,
+ bool preserve_contents) {
+ ASSERT(index < Tegra::Engines::Maxwell3D::Regs::NumRenderTargets);
+
+ const auto& regs{system.GPU().Maxwell3D().regs};
+ if (index >= regs.rt_control.count || regs.rt[index].Address() == 0 ||
+ regs.rt[index].format == Tegra::RenderTargetFormat::NONE) {
+ return {{}, exctx};
+ }
+
+ auto& memory_manager{system.GPU().MemoryManager()};
+ const auto& config{system.GPU().Maxwell3D().regs.rt[index]};
+ const auto cpu_addr{memory_manager.GpuToCpuAddress(
+ config.Address() + config.base_layer * config.layer_stride * sizeof(u32))};
+ if (!cpu_addr) {
+ return {{}, exctx};
+ }
+
+ return GetSurfaceView(exctx, *cpu_addr, SurfaceParams::CreateForFramebuffer(system, index),
+ preserve_contents);
+ }
+
+ ResultType GetFermiSurface(TExecutionContext exctx,
+ const Tegra::Engines::Fermi2D::Regs::Surface& config) {
+ const auto cpu_addr{system.GPU().MemoryManager().GpuToCpuAddress(config.Address())};
+ ASSERT(cpu_addr);
+ return GetSurfaceView(exctx, *cpu_addr, SurfaceParams::CreateForFermiCopySurface(config),
+ true);
+ }
+
+ TSurface* TryFindFramebufferSurface(const u8* host_ptr) const {
+ const auto it{registered_surfaces.find(ToCacheAddr(host_ptr))};
+ return it != registered_surfaces.end() ? *it->second.begin() : nullptr;
+ }
+
+protected:
+ TextureCache(Core::System& system, VideoCore::RasterizerInterface& rasterizer)
+ : system{system}, rasterizer{rasterizer} {}
+
+ ~TextureCache() = default;
+
+ virtual ResultType TryFastGetSurfaceView(TExecutionContext exctx, VAddr cpu_addr, u8* host_ptr,
+ const SurfaceParams& params, bool preserve_contents,
+ const std::vector<TSurface*>& overlaps) = 0;
+
+ virtual std::unique_ptr<TSurface> CreateSurface(const SurfaceParams& params) = 0;
+
+ void Register(TSurface* surface, VAddr cpu_addr, u8* host_ptr) {
+ surface->Register(cpu_addr, host_ptr);
+ registered_surfaces.add({GetSurfaceInterval(surface), {surface}});
+ rasterizer.UpdatePagesCachedCount(surface->GetCpuAddr(), surface->GetSizeInBytes(), 1);
+ }
+
+ void Unregister(TSurface* surface) {
+ registered_surfaces.subtract({GetSurfaceInterval(surface), {surface}});
+ rasterizer.UpdatePagesCachedCount(surface->GetCpuAddr(), surface->GetSizeInBytes(), -1);
+ surface->Unregister();
+ }
+
+ TSurface* GetUncachedSurface(const SurfaceParams& params) {
+ if (TSurface* surface = TryGetReservedSurface(params); surface)
+ return surface;
+ // No reserved surface available, create a new one and reserve it
+ auto new_surface{CreateSurface(params)};
+ TSurface* surface{new_surface.get()};
+ ReserveSurface(params, std::move(new_surface));
+ return surface;
+ }
+
+ Core::System& system;
+
+private:
+ ResultType GetSurfaceView(TExecutionContext exctx, VAddr cpu_addr, const SurfaceParams& params,
+ bool preserve_contents) {
+ const auto host_ptr{Memory::GetPointer(cpu_addr)};
+ const auto cache_addr{ToCacheAddr(host_ptr)};
+ const auto overlaps{GetSurfacesInRegion(cache_addr, params.GetGuestSizeInBytes())};
+ if (overlaps.empty()) {
+ return LoadSurfaceView(exctx, cpu_addr, host_ptr, params, preserve_contents);
+ }
+
+ if (overlaps.size() == 1) {
+ if (TView* view = overlaps[0]->TryGetView(cpu_addr, params); view)
+ return {view, exctx};
+ }
+
+ TView* fast_view;
+ std::tie(fast_view, exctx) =
+ TryFastGetSurfaceView(exctx, cpu_addr, host_ptr, params, preserve_contents, overlaps);
+
+ for (TSurface* surface : overlaps) {
+ if (!fast_view) {
+ // Flush even when we don't care about the contents, to preserve memory not written
+ // by the new surface.
+ exctx = surface->FlushBuffer(exctx);
+ }
+ Unregister(surface);
+ }
+
+ if (fast_view) {
+ return {fast_view, exctx};
+ }
+
+ return LoadSurfaceView(exctx, cpu_addr, host_ptr, params, preserve_contents);
+ }
+
+ ResultType LoadSurfaceView(TExecutionContext exctx, VAddr cpu_addr, u8* host_ptr,
+ const SurfaceParams& params, bool preserve_contents) {
+ TSurface* new_surface{GetUncachedSurface(params)};
+ Register(new_surface, cpu_addr, host_ptr);
+ if (preserve_contents) {
+ exctx = LoadSurface(exctx, new_surface);
+ }
+ return {new_surface->GetView(cpu_addr, params), exctx};
+ }
+
+ TExecutionContext LoadSurface(TExecutionContext exctx, TSurface* surface) {
+ surface->LoadBuffer();
+ exctx = surface->UploadTexture(exctx);
+ surface->MarkAsModified(false);
+ return exctx;
+ }
+
+ std::vector<TSurface*> GetSurfacesInRegion(CacheAddr cache_addr, std::size_t size) const {
+ if (size == 0) {
+ return {};
+ }
+ const IntervalType interval{cache_addr, cache_addr + size};
+
+ std::vector<TSurface*> surfaces;
+ for (auto& pair : boost::make_iterator_range(registered_surfaces.equal_range(interval))) {
+ surfaces.push_back(*pair.second.begin());
+ }
+ return surfaces;
+ }
+
+ void ReserveSurface(const SurfaceParams& params, std::unique_ptr<TSurface> surface) {
+ surface_reserve[params].push_back(std::move(surface));
+ }
+
+ TSurface* TryGetReservedSurface(const SurfaceParams& params) {
+ auto search{surface_reserve.find(params)};
+ if (search == surface_reserve.end()) {
+ return {};
+ }
+ for (auto& surface : search->second) {
+ if (!surface->IsRegistered()) {
+ return surface.get();
+ }
+ }
+ return {};
+ }
+
+ IntervalType GetSurfaceInterval(TSurface* surface) const {
+ return IntervalType::right_open(surface->GetCacheAddr(),
+ surface->GetCacheAddr() + surface->GetSizeInBytes());
+ }
+
+ VideoCore::RasterizerInterface& rasterizer;
+
+ IntervalMap registered_surfaces;
+
+ /// 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::list<std::unique_ptr<TSurface>>> surface_reserve;
+};
+
+} // namespace VideoCommon
diff --git a/src/video_core/textures/astc.cpp b/src/video_core/textures/astc.cpp
index b508d64e9..a9b8f69af 100644
--- a/src/video_core/textures/astc.cpp
+++ b/src/video_core/textures/astc.cpp
@@ -25,8 +25,8 @@
class InputBitStream {
public:
- explicit InputBitStream(const unsigned char* ptr, int nBits = 0, int start_offset = 0)
- : m_NumBits(nBits), m_CurByte(ptr), m_NextBit(start_offset % 8) {}
+ explicit InputBitStream(const unsigned char* ptr, int start_offset = 0)
+ : m_CurByte(ptr), m_NextBit(start_offset % 8) {}
~InputBitStream() = default;
@@ -55,12 +55,9 @@ public:
}
private:
- const int m_NumBits;
const unsigned char* m_CurByte;
int m_NextBit = 0;
int m_BitsRead = 0;
-
- bool done = false;
};
class OutputBitStream {
@@ -114,7 +111,6 @@ private:
const int m_NumBits;
unsigned char* m_CurByte;
int m_NextBit = 0;
- int m_BitsRead = 0;
bool done = false;
};
@@ -1616,6 +1612,7 @@ 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) {
uint32_t blockIdx = 0;
+ std::size_t depth_offset = 0;
std::vector<uint8_t> outData(height * width * depth * 4);
for (uint32_t k = 0; k < depth; k++) {
for (uint32_t j = 0; j < height; j += block_height) {
@@ -1630,7 +1627,7 @@ std::vector<uint8_t> Decompress(const uint8_t* data, uint32_t width, uint32_t he
uint32_t decompWidth = std::min(block_width, width - i);
uint32_t decompHeight = std::min(block_height, height - j);
- uint8_t* outRow = outData.data() + (j * width + i) * 4;
+ uint8_t* outRow = depth_offset + outData.data() + (j * width + i) * 4;
for (uint32_t jj = 0; jj < decompHeight; jj++) {
memcpy(outRow + jj * width * 4, uncompData + jj * block_width, decompWidth * 4);
}
@@ -1638,6 +1635,7 @@ std::vector<uint8_t> Decompress(const uint8_t* data, uint32_t width, uint32_t he
blockIdx++;
}
}
+ depth_offset += height * width * 4;
}
return outData;
diff --git a/src/video_core/textures/convert.cpp b/src/video_core/textures/convert.cpp
index 5e439f036..82050bd51 100644
--- a/src/video_core/textures/convert.cpp
+++ b/src/video_core/textures/convert.cpp
@@ -10,6 +10,7 @@
#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"
diff --git a/src/video_core/textures/convert.h b/src/video_core/textures/convert.h
index 07cd8b5da..12542e71c 100644
--- a/src/video_core/textures/convert.h
+++ b/src/video_core/textures/convert.h
@@ -5,7 +5,10 @@
#pragma once
#include "common/common_types.h"
-#include "video_core/surface.h"
+
+namespace VideoCore::Surface {
+enum class PixelFormat;
+}
namespace Tegra::Texture {
diff --git a/src/video_core/textures/decoders.cpp b/src/video_core/textures/decoders.cpp
index cad7340f5..217805386 100644
--- a/src/video_core/textures/decoders.cpp
+++ b/src/video_core/textures/decoders.cpp
@@ -6,7 +6,6 @@
#include <cstring>
#include "common/alignment.h"
#include "common/assert.h"
-#include "core/memory.h"
#include "video_core/gpu.h"
#include "video_core/textures/decoders.h"
#include "video_core/textures/texture.h"
@@ -230,18 +229,18 @@ u32 BytesPerPixel(TextureFormat format) {
}
}
-void UnswizzleTexture(u8* const unswizzled_data, VAddr address, u32 tile_size_x, u32 tile_size_y,
+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, Memory::GetPointer(address), unswizzled_data, true,
- block_height, block_depth, width_spacing);
+ bytes_per_pixel, address, unswizzled_data, true, block_height, block_depth,
+ width_spacing);
}
-std::vector<u8> UnswizzleTexture(VAddr 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> 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);
@@ -249,8 +248,7 @@ std::vector<u8> UnswizzleTexture(VAddr address, u32 tile_size_x, u32 tile_size_y
}
void SwizzleSubrect(u32 subrect_width, u32 subrect_height, u32 source_pitch, u32 swizzled_width,
- u32 bytes_per_pixel, VAddr swizzled_data, VAddr unswizzled_data,
- u32 block_height) {
+ u32 bytes_per_pixel, u8* swizzled_data, u8* unswizzled_data, u32 block_height) {
const u32 image_width_in_gobs{(swizzled_width * bytes_per_pixel + (gob_size_x - 1)) /
gob_size_x};
for (u32 line = 0; line < subrect_height; ++line) {
@@ -262,17 +260,17 @@ void SwizzleSubrect(u32 subrect_width, u32 subrect_height, u32 source_pitch, u32
const u32 gob_address =
gob_address_y + (x * bytes_per_pixel / gob_size_x) * gob_size * block_height;
const u32 swizzled_offset = gob_address + table[(x * bytes_per_pixel) % gob_size_x];
- const VAddr source_line = unswizzled_data + line * source_pitch + x * bytes_per_pixel;
- const VAddr dest_addr = swizzled_data + swizzled_offset;
+ u8* source_line = unswizzled_data + line * source_pitch + x * bytes_per_pixel;
+ u8* dest_addr = swizzled_data + swizzled_offset;
- Memory::CopyBlock(dest_addr, source_line, bytes_per_pixel);
+ std::memcpy(dest_addr, source_line, bytes_per_pixel);
}
}
}
void UnswizzleSubrect(u32 subrect_width, u32 subrect_height, u32 dest_pitch, u32 swizzled_width,
- u32 bytes_per_pixel, VAddr swizzled_data, VAddr unswizzled_data,
- u32 block_height, u32 offset_x, u32 offset_y) {
+ u32 bytes_per_pixel, u8* swizzled_data, u8* unswizzled_data, u32 block_height,
+ u32 offset_x, u32 offset_y) {
for (u32 line = 0; line < subrect_height; ++line) {
const u32 y2 = line + offset_y;
const u32 gob_address_y = (y2 / (gob_size_y * block_height)) * gob_size * block_height +
@@ -282,10 +280,33 @@ void UnswizzleSubrect(u32 subrect_width, u32 subrect_height, u32 dest_pitch, u32
const u32 x2 = (x + offset_x) * bytes_per_pixel;
const u32 gob_address = gob_address_y + (x2 / gob_size_x) * gob_size * block_height;
const u32 swizzled_offset = gob_address + table[x2 % gob_size_x];
- const VAddr dest_line = unswizzled_data + line * dest_pitch + x * bytes_per_pixel;
- const VAddr source_addr = swizzled_data + swizzled_offset;
+ u8* dest_line = unswizzled_data + line * dest_pitch + x * bytes_per_pixel;
+ u8* source_addr = swizzled_data + swizzled_offset;
- Memory::CopyBlock(dest_line, source_addr, bytes_per_pixel);
+ std::memcpy(dest_line, source_addr, bytes_per_pixel);
+ }
+ }
+}
+
+void SwizzleKepler(const u32 width, const u32 height, const u32 dst_x, const u32 dst_y,
+ const u32 block_height, const std::size_t copy_size, const u8* source_data,
+ u8* swizzle_data) {
+ const u32 image_width_in_gobs{(width + gob_size_x - 1) / gob_size_x};
+ std::size_t count = 0;
+ for (std::size_t y = dst_y; y < height && count < copy_size; ++y) {
+ 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];
+ 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;
+ const std::size_t swizzled_offset = gob_address + table[x % gob_size_x];
+ const u8* source_line = source_data + count;
+ u8* dest_addr = swizzle_data + swizzled_offset;
+ count++;
+
+ std::memcpy(dest_addr, source_line, 1);
}
}
}
diff --git a/src/video_core/textures/decoders.h b/src/video_core/textures/decoders.h
index 65df86890..e072d8401 100644
--- a/src/video_core/textures/decoders.h
+++ b/src/video_core/textures/decoders.h
@@ -17,14 +17,14 @@ inline std::size_t GetGOBSize() {
}
/// Unswizzles a swizzled texture without changing its format.
-void UnswizzleTexture(u8* unswizzled_data, VAddr address, u32 tile_size_x, u32 tile_size_y,
+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(VAddr address, u32 tile_size_x, u32 tile_size_y,
- u32 bytes_per_pixel, u32 width, u32 height, u32 depth,
+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);
@@ -44,12 +44,15 @@ std::size_t CalculateSize(bool tiled, u32 bytes_per_pixel, u32 width, u32 height
/// Copies an untiled subrectangle into a tiled surface.
void SwizzleSubrect(u32 subrect_width, u32 subrect_height, u32 source_pitch, u32 swizzled_width,
- u32 bytes_per_pixel, VAddr swizzled_data, VAddr unswizzled_data,
- u32 block_height);
+ u32 bytes_per_pixel, u8* swizzled_data, u8* unswizzled_data, u32 block_height);
/// Copies a tiled subrectangle into a linear surface.
void UnswizzleSubrect(u32 subrect_width, u32 subrect_height, u32 dest_pitch, u32 swizzled_width,
- u32 bytes_per_pixel, VAddr swizzled_data, VAddr unswizzled_data,
- u32 block_height, u32 offset_x, u32 offset_y);
+ u32 bytes_per_pixel, u8* swizzled_data, u8* unswizzled_data, u32 block_height,
+ u32 offset_x, u32 offset_y);
+
+void SwizzleKepler(const u32 width, const u32 height, const u32 dst_x, const u32 dst_y,
+ const u32 block_height, const std::size_t copy_size, const u8* source_data,
+ u8* swizzle_data);
} // namespace Tegra::Texture
diff --git a/src/video_core/textures/texture.h b/src/video_core/textures/texture.h
index 0fc5530f2..219bfd559 100644
--- a/src/video_core/textures/texture.h
+++ b/src/video_core/textures/texture.h
@@ -4,11 +4,10 @@
#pragma once
+#include <array>
#include "common/assert.h"
#include "common/bit_field.h"
-#include "common/common_funcs.h"
#include "common/common_types.h"
-#include "video_core/memory_manager.h"
namespace Tegra::Texture {
@@ -252,7 +251,7 @@ enum class WrapMode : u32 {
Mirror = 1,
ClampToEdge = 2,
Border = 3,
- ClampOGL = 4,
+ Clamp = 4,
MirrorOnceClampToEdge = 5,
MirrorOnceBorder = 6,
MirrorOnceClampOGL = 7,
@@ -282,34 +281,62 @@ enum class TextureMipmapFilter : u32 {
struct TSCEntry {
union {
- BitField<0, 3, WrapMode> wrap_u;
- BitField<3, 3, WrapMode> wrap_v;
- BitField<6, 3, WrapMode> wrap_p;
- BitField<9, 1, u32> depth_compare_enabled;
- BitField<10, 3, DepthCompareFunc> depth_compare_func;
- BitField<13, 1, u32> srgb_conversion;
- BitField<20, 3, u32> max_anisotropy;
+ struct {
+ union {
+ BitField<0, 3, WrapMode> wrap_u;
+ BitField<3, 3, WrapMode> wrap_v;
+ BitField<6, 3, WrapMode> wrap_p;
+ BitField<9, 1, u32> depth_compare_enabled;
+ BitField<10, 3, DepthCompareFunc> depth_compare_func;
+ BitField<13, 1, u32> srgb_conversion;
+ BitField<20, 3, u32> max_anisotropy;
+ };
+ union {
+ BitField<0, 2, TextureFilter> mag_filter;
+ BitField<4, 2, TextureFilter> min_filter;
+ BitField<6, 2, TextureMipmapFilter> mipmap_filter;
+ BitField<9, 1, u32> cubemap_interface_filtering;
+ BitField<12, 13, u32> mip_lod_bias;
+ };
+ union {
+ BitField<0, 12, u32> min_lod_clamp;
+ BitField<12, 12, u32> max_lod_clamp;
+ BitField<24, 8, u32> srgb_border_color_r;
+ };
+ union {
+ BitField<12, 8, u32> srgb_border_color_g;
+ BitField<20, 8, u32> srgb_border_color_b;
+ };
+ std::array<f32, 4> border_color;
+ };
+ std::array<u8, 0x20> raw;
};
- union {
- BitField<0, 2, TextureFilter> mag_filter;
- BitField<4, 2, TextureFilter> min_filter;
- BitField<6, 2, TextureMipmapFilter> mip_filter;
- BitField<9, 1, u32> cubemap_interface_filtering;
- BitField<12, 13, u32> mip_lod_bias;
- };
- union {
- BitField<0, 12, u32> min_lod_clamp;
- BitField<12, 12, u32> max_lod_clamp;
- BitField<24, 8, u32> srgb_border_color_r;
- };
- union {
- BitField<12, 8, u32> srgb_border_color_g;
- BitField<20, 8, u32> srgb_border_color_b;
- };
- float border_color_r;
- float border_color_g;
- float border_color_b;
- float border_color_a;
+
+ float GetMaxAnisotropy() const {
+ return static_cast<float>(1U << max_anisotropy);
+ }
+
+ float GetMinLod() const {
+ return static_cast<float>(min_lod_clamp) / 256.0f;
+ }
+
+ float GetMaxLod() const {
+ return static_cast<float>(max_lod_clamp) / 256.0f;
+ }
+
+ float GetLodBias() const {
+ // Sign extend the 13-bit value.
+ constexpr u32 mask = 1U << (13 - 1);
+ return static_cast<s32>((mip_lod_bias ^ mask) - mask) / 256.0f;
+ }
+
+ std::array<float, 4> GetBorderColor() const {
+ if (srgb_conversion) {
+ return {srgb_border_color_r / 255.0f, srgb_border_color_g / 255.0f,
+ srgb_border_color_b / 255.0f, border_color[3]};
+ }
+ return border_color;
+ }
};
static_assert(sizeof(TSCEntry) == 0x20, "TSCEntry has wrong size");
diff --git a/src/video_core/video_core.cpp b/src/video_core/video_core.cpp
index cb82ecf3f..60cda0ca3 100644
--- a/src/video_core/video_core.cpp
+++ b/src/video_core/video_core.cpp
@@ -5,6 +5,8 @@
#include <memory>
#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"
#include "video_core/video_core.h"
@@ -16,6 +18,14 @@ std::unique_ptr<RendererBase> CreateRenderer(Core::Frontend::EmuWindow& emu_wind
return std::make_unique<OpenGL::RendererOpenGL>(emu_window, system);
}
+std::unique_ptr<Tegra::GPU> CreateGPU(Core::System& system) {
+ if (Settings::values.use_asynchronous_gpu_emulation) {
+ return std::make_unique<VideoCommon::GPUAsynch>(system, system.Renderer());
+ }
+
+ return std::make_unique<VideoCommon::GPUSynch>(system, system.Renderer());
+}
+
u16 GetResolutionScaleFactor(const RendererBase& renderer) {
return static_cast<u16>(
Settings::values.resolution_factor
diff --git a/src/video_core/video_core.h b/src/video_core/video_core.h
index 3c583f195..b8e0ac372 100644
--- a/src/video_core/video_core.h
+++ b/src/video_core/video_core.h
@@ -14,6 +14,10 @@ namespace Core::Frontend {
class EmuWindow;
}
+namespace Tegra {
+class GPU;
+}
+
namespace VideoCore {
class RendererBase;
@@ -27,6 +31,9 @@ class RendererBase;
std::unique_ptr<RendererBase> CreateRenderer(Core::Frontend::EmuWindow& emu_window,
Core::System& system);
+/// Creates an emulated GPU instance using the given system context.
+std::unique_ptr<Tegra::GPU> CreateGPU(Core::System& system);
+
u16 GetResolutionScaleFactor(const RendererBase& renderer);
} // namespace VideoCore
diff --git a/src/web_service/web_backend.cpp b/src/web_service/web_backend.cpp
index 40da1a4e2..dc149d2ed 100644
--- a/src/web_service/web_backend.cpp
+++ b/src/web_service/web_backend.cpp
@@ -24,7 +24,7 @@ constexpr u32 TIMEOUT_SECONDS = 30;
struct Client::Impl {
Impl(std::string host, std::string username, std::string token)
: host{std::move(host)}, username{std::move(username)}, token{std::move(token)} {
- std::lock_guard<std::mutex> lock(jwt_cache.mutex);
+ std::lock_guard lock{jwt_cache.mutex};
if (this->username == jwt_cache.username && this->token == jwt_cache.token) {
jwt = jwt_cache.jwt;
}
@@ -151,7 +151,7 @@ struct Client::Impl {
if (result.result_code != Common::WebResult::Code::Success) {
LOG_ERROR(WebService, "UpdateJWT failed");
} else {
- std::lock_guard<std::mutex> lock(jwt_cache.mutex);
+ std::lock_guard lock{jwt_cache.mutex};
jwt_cache.username = username;
jwt_cache.token = token;
jwt_cache.jwt = jwt = result.returned_data;
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 4cab599b4..3dc0e47d0 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -7,6 +7,8 @@ add_executable(yuzu
Info.plist
about_dialog.cpp
about_dialog.h
+ applets/error.cpp
+ applets/error.h
applets/profile_select.cpp
applets/profile_select.h
applets/software_keyboard.cpp
@@ -31,6 +33,8 @@ add_executable(yuzu
configuration/configure_general.h
configuration/configure_graphics.cpp
configuration/configure_graphics.h
+ configuration/configure_hotkeys.cpp
+ configuration/configure_hotkeys.h
configuration/configure_input.cpp
configuration/configure_input.h
configuration/configure_input_player.cpp
@@ -54,8 +58,6 @@ add_executable(yuzu
debugger/graphics/graphics_breakpoints.cpp
debugger/graphics/graphics_breakpoints.h
debugger/graphics/graphics_breakpoints_p.h
- debugger/graphics/graphics_surface.cpp
- debugger/graphics/graphics_surface.h
debugger/console.cpp
debugger/console.h
debugger/profiler.cpp
@@ -78,8 +80,8 @@ add_executable(yuzu
ui_settings.h
util/limitable_input_dialog.cpp
util/limitable_input_dialog.h
- util/spinbox.cpp
- util/spinbox.h
+ util/sequence_dialog/sequence_dialog.cpp
+ util/sequence_dialog/sequence_dialog.h
util/util.cpp
util/util.h
compatdb.cpp
@@ -95,6 +97,7 @@ set(UIS
configuration/configure_gamelist.ui
configuration/configure_general.ui
configuration/configure_graphics.ui
+ configuration/configure_hotkeys.ui
configuration/configure_input.ui
configuration/configure_input_player.ui
configuration/configure_input_simple.ui
@@ -105,7 +108,6 @@ set(UIS
configuration/configure_touchscreen_advanced.ui
configuration/configure_web.ui
compatdb.ui
- hotkeys.ui
loading_screen.ui
main.ui
)
@@ -149,6 +151,25 @@ target_link_libraries(yuzu PRIVATE common core input_common video_core)
target_link_libraries(yuzu PRIVATE Boost::boost glad Qt5::OpenGL Qt5::Widgets)
target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
+target_compile_definitions(yuzu PRIVATE
+ # Use QStringBuilder for string concatenation to reduce
+ # the overall number of temporary strings created.
+ -DQT_USE_QSTRINGBUILDER
+
+ # Disable implicit conversions from/to C strings
+ -DQT_NO_CAST_FROM_ASCII
+ -DQT_NO_CAST_TO_ASCII
+
+ # Disable implicit type narrowing in signal/slot connect() calls.
+ -DQT_NO_NARROWING_CONVERSIONS_IN_CONNECT
+
+ # Disable unsafe overloads of QProcess' start() function.
+ -DQT_NO_PROCESS_COMBINED_ARGUMENT_START
+
+ # Disable implicit QString->QUrl conversions to enforce use of proper resolving functions.
+ -DQT_NO_URL_CAST_FROM_STRING
+)
+
if (YUZU_ENABLE_COMPATIBILITY_REPORTING)
target_compile_definitions(yuzu PRIVATE -DYUZU_ENABLE_COMPATIBILITY_REPORTING)
endif()
diff --git a/src/yuzu/about_dialog.cpp b/src/yuzu/about_dialog.cpp
index 3efa65a38..d39b3f07a 100644
--- a/src/yuzu/about_dialog.cpp
+++ b/src/yuzu/about_dialog.cpp
@@ -9,10 +9,10 @@
AboutDialog::AboutDialog(QWidget* parent) : QDialog(parent), ui(new Ui::AboutDialog) {
ui->setupUi(this);
- ui->labelLogo->setPixmap(QIcon::fromTheme("yuzu").pixmap(200));
- ui->labelBuildInfo->setText(
- ui->labelBuildInfo->text().arg(Common::g_build_fullname, Common::g_scm_branch,
- Common::g_scm_desc, QString(Common::g_build_date).left(10)));
+ ui->labelLogo->setPixmap(QIcon::fromTheme(QStringLiteral("yuzu")).pixmap(200));
+ ui->labelBuildInfo->setText(ui->labelBuildInfo->text().arg(
+ QString::fromUtf8(Common::g_build_fullname), QString::fromUtf8(Common::g_scm_branch),
+ QString::fromUtf8(Common::g_scm_desc), QString::fromUtf8(Common::g_build_date).left(10)));
}
AboutDialog::~AboutDialog() = default;
diff --git a/src/yuzu/applets/error.cpp b/src/yuzu/applets/error.cpp
new file mode 100644
index 000000000..08ed57355
--- /dev/null
+++ b/src/yuzu/applets/error.cpp
@@ -0,0 +1,61 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <QDateTime>
+#include "core/hle/lock.h"
+#include "yuzu/applets/error.h"
+#include "yuzu/main.h"
+
+QtErrorDisplay::QtErrorDisplay(GMainWindow& parent) {
+ connect(this, &QtErrorDisplay::MainWindowDisplayError, &parent,
+ &GMainWindow::ErrorDisplayDisplayError, Qt::QueuedConnection);
+ connect(&parent, &GMainWindow::ErrorDisplayFinished, this,
+ &QtErrorDisplay::MainWindowFinishedError, Qt::DirectConnection);
+}
+
+QtErrorDisplay::~QtErrorDisplay() = default;
+
+void QtErrorDisplay::ShowError(ResultCode error, std::function<void()> finished) const {
+ this->callback = std::move(finished);
+ emit MainWindowDisplayError(
+ tr("An error has occured.\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'))
+ .arg(error.raw, 8, 16, QChar::fromLatin1('0')));
+}
+
+void QtErrorDisplay::ShowErrorWithTimestamp(ResultCode error, std::chrono::seconds time,
+ std::function<void()> finished) const {
+ this->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 "
+ "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")))
+ .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')));
+}
+
+void QtErrorDisplay::ShowCustomErrorText(ResultCode error, std::string dialog_text,
+ std::string fullscreen_text,
+ std::function<void()> finished) const {
+ this->callback = std::move(finished);
+ emit MainWindowDisplayError(
+ tr("An error has occured.\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'))
+ .arg(QString::fromStdString(dialog_text))
+ .arg(QString::fromStdString(fullscreen_text)));
+}
+
+void QtErrorDisplay::MainWindowFinishedError() {
+ // Acquire the HLE mutex
+ std::lock_guard lock{HLE::g_hle_lock};
+ callback();
+}
diff --git a/src/yuzu/applets/error.h b/src/yuzu/applets/error.h
new file mode 100644
index 000000000..b0932d895
--- /dev/null
+++ b/src/yuzu/applets/error.h
@@ -0,0 +1,33 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <QObject>
+
+#include "core/frontend/applets/error.h"
+
+class GMainWindow;
+
+class QtErrorDisplay final : public QObject, public Core::Frontend::ErrorApplet {
+ Q_OBJECT
+
+public:
+ explicit QtErrorDisplay(GMainWindow& parent);
+ ~QtErrorDisplay() override;
+
+ void ShowError(ResultCode error, std::function<void()> finished) const override;
+ void ShowErrorWithTimestamp(ResultCode error, std::chrono::seconds time,
+ std::function<void()> finished) const override;
+ void ShowCustomErrorText(ResultCode error, std::string dialog_text, std::string fullscreen_text,
+ std::function<void()> finished) const override;
+
+signals:
+ void MainWindowDisplayError(QString error) const;
+
+private:
+ void MainWindowFinishedError();
+
+ mutable std::function<void()> callback;
+};
diff --git a/src/yuzu/applets/profile_select.cpp b/src/yuzu/applets/profile_select.cpp
index 5c1b65a2c..6aff38735 100644
--- a/src/yuzu/applets/profile_select.cpp
+++ b/src/yuzu/applets/profile_select.cpp
@@ -4,6 +4,7 @@
#include <mutex>
#include <QDialogButtonBox>
+#include <QHeaderView>
#include <QLabel>
#include <QLineEdit>
#include <QScrollArea>
@@ -11,40 +12,31 @@
#include <QVBoxLayout>
#include "common/file_util.h"
#include "common/string_util.h"
+#include "core/constants.h"
#include "core/hle/lock.h"
#include "yuzu/applets/profile_select.h"
#include "yuzu/main.h"
-// Same backup JPEG used by acc IProfile::GetImage if no jpeg found
-constexpr std::array<u8, 107> backup_jpeg{
- 0xff, 0xd8, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x02, 0x02,
- 0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x06, 0x04, 0x04, 0x04, 0x04, 0x04, 0x08, 0x06, 0x06, 0x05,
- 0x06, 0x09, 0x08, 0x0a, 0x0a, 0x09, 0x08, 0x09, 0x09, 0x0a, 0x0c, 0x0f, 0x0c, 0x0a, 0x0b, 0x0e,
- 0x0b, 0x09, 0x09, 0x0d, 0x11, 0x0d, 0x0e, 0x0f, 0x10, 0x10, 0x11, 0x10, 0x0a, 0x0c, 0x12, 0x13,
- 0x12, 0x10, 0x13, 0x0f, 0x10, 0x10, 0x10, 0xff, 0xc9, 0x00, 0x0b, 0x08, 0x00, 0x01, 0x00, 0x01,
- 0x01, 0x01, 0x11, 0x00, 0xff, 0xcc, 0x00, 0x06, 0x00, 0x10, 0x10, 0x05, 0xff, 0xda, 0x00, 0x08,
- 0x01, 0x01, 0x00, 0x00, 0x3f, 0x00, 0xd2, 0xcf, 0x20, 0xff, 0xd9,
-};
-
-QString FormatUserEntryText(const QString& username, Service::Account::UUID uuid) {
+QString FormatUserEntryText(const QString& username, Common::UUID uuid) {
return QtProfileSelectionDialog::tr(
"%1\n%2", "%1 is the profile username, %2 is the formatted UUID (e.g. "
"00112233-4455-6677-8899-AABBCCDDEEFF))")
.arg(username, QString::fromStdString(uuid.FormatSwitch()));
}
-QString GetImagePath(Service::Account::UUID uuid) {
+QString GetImagePath(Common::UUID uuid) {
const auto path = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
"/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg";
return QString::fromStdString(path);
}
-QPixmap GetIcon(Service::Account::UUID uuid) {
+QPixmap GetIcon(Common::UUID uuid) {
QPixmap icon{GetImagePath(uuid)};
if (!icon) {
icon.fill(Qt::black);
- icon.loadFromData(backup_jpeg.data(), static_cast<u32>(backup_jpeg.size()));
+ icon.loadFromData(Core::Constants::ACCOUNT_BACKUP_JPEG.data(),
+ static_cast<u32>(Core::Constants::ACCOUNT_BACKUP_JPEG.size()));
}
return icon.scaled(64, 64, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
@@ -58,10 +50,7 @@ QtProfileSelectionDialog::QtProfileSelectionDialog(QWidget* parent)
scroll_area = new QScrollArea;
- buttons = new QDialogButtonBox;
- buttons->addButton(tr("Cancel"), QDialogButtonBox::RejectRole);
- buttons->addButton(tr("OK"), QDialogButtonBox::AcceptRole);
-
+ buttons = new QDialogButtonBox(QDialogButtonBox::Cancel | QDialogButtonBox::Ok);
connect(buttons, &QDialogButtonBox::accepted, this, &QtProfileSelectionDialog::accept);
connect(buttons, &QDialogButtonBox::rejected, this, &QtProfileSelectionDialog::reject);
@@ -86,10 +75,10 @@ QtProfileSelectionDialog::QtProfileSelectionDialog(QWidget* parent)
tree_view->setContextMenuPolicy(Qt::NoContextMenu);
item_model->insertColumns(0, 1);
- item_model->setHeaderData(0, Qt::Horizontal, "Users");
+ item_model->setHeaderData(0, Qt::Horizontal, tr("Users"));
// 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.
+ // with signals/slots. In this case, QList falls under the umbrella of custom types.
qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>");
layout->setContentsMargins(0, 0, 0, 0);
@@ -124,21 +113,15 @@ QtProfileSelectionDialog::QtProfileSelectionDialog(QWidget* parent)
QtProfileSelectionDialog::~QtProfileSelectionDialog() = default;
void QtProfileSelectionDialog::accept() {
- ok = true;
QDialog::accept();
}
void QtProfileSelectionDialog::reject() {
- ok = false;
user_index = 0;
QDialog::reject();
}
-bool QtProfileSelectionDialog::GetStatus() const {
- return ok;
-}
-
-u32 QtProfileSelectionDialog::GetIndex() const {
+int QtProfileSelectionDialog::GetIndex() const {
return user_index;
}
@@ -156,13 +139,13 @@ QtProfileSelector::QtProfileSelector(GMainWindow& parent) {
QtProfileSelector::~QtProfileSelector() = default;
void QtProfileSelector::SelectProfile(
- std::function<void(std::optional<Service::Account::UUID>)> callback) const {
+ std::function<void(std::optional<Common::UUID>)> callback) const {
this->callback = std::move(callback);
emit MainWindowSelectProfile();
}
-void QtProfileSelector::MainWindowFinishedSelection(std::optional<Service::Account::UUID> uuid) {
+void QtProfileSelector::MainWindowFinishedSelection(std::optional<Common::UUID> uuid) {
// Acquire the HLE mutex
- std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
+ std::lock_guard lock{HLE::g_hle_lock};
callback(uuid);
}
diff --git a/src/yuzu/applets/profile_select.h b/src/yuzu/applets/profile_select.h
index 868573324..cee886a77 100644
--- a/src/yuzu/applets/profile_select.h
+++ b/src/yuzu/applets/profile_select.h
@@ -7,7 +7,9 @@
#include <vector>
#include <QDialog>
#include <QList>
+#include <QTreeView>
#include "core/frontend/applets/profile_select.h"
+#include "core/hle/service/acc/profile_manager.h"
class GMainWindow;
class QDialogButtonBox;
@@ -16,7 +18,6 @@ class QLabel;
class QScrollArea;
class QStandardItem;
class QStandardItemModel;
-class QTreeView;
class QVBoxLayout;
class QtProfileSelectionDialog final : public QDialog {
@@ -29,15 +30,13 @@ public:
void accept() override;
void reject() override;
- bool GetStatus() const;
- u32 GetIndex() const;
+ int GetIndex() const;
private:
- bool ok = false;
- u32 user_index = 0;
-
void SelectUser(const QModelIndex& index);
+ int user_index = 0;
+
QVBoxLayout* layout;
QTreeView* tree_view;
QStandardItemModel* item_model;
@@ -60,14 +59,13 @@ public:
explicit QtProfileSelector(GMainWindow& parent);
~QtProfileSelector() override;
- void SelectProfile(
- std::function<void(std::optional<Service::Account::UUID>)> callback) const override;
+ void SelectProfile(std::function<void(std::optional<Common::UUID>)> callback) const override;
signals:
void MainWindowSelectProfile() const;
private:
- void MainWindowFinishedSelection(std::optional<Service::Account::UUID> uuid);
+ void MainWindowFinishedSelection(std::optional<Common::UUID> uuid);
- mutable std::function<void(std::optional<Service::Account::UUID>)> callback;
+ mutable std::function<void(std::optional<Common::UUID>)> callback;
};
diff --git a/src/yuzu/applets/software_keyboard.cpp b/src/yuzu/applets/software_keyboard.cpp
index 8a26fdff1..af36f07c6 100644
--- a/src/yuzu/applets/software_keyboard.cpp
+++ b/src/yuzu/applets/software_keyboard.cpp
@@ -18,23 +18,30 @@ QtSoftwareKeyboardValidator::QtSoftwareKeyboardValidator(
: parameters(std::move(parameters)) {}
QValidator::State QtSoftwareKeyboardValidator::validate(QString& input, int& pos) const {
- if (input.size() > parameters.max_length)
+ if (input.size() > static_cast<s64>(parameters.max_length)) {
return Invalid;
- if (parameters.disable_space && input.contains(' '))
+ }
+ if (parameters.disable_space && input.contains(QLatin1Char{' '})) {
return Invalid;
- if (parameters.disable_address && input.contains('@'))
+ }
+ if (parameters.disable_address && input.contains(QLatin1Char{'@'})) {
return Invalid;
- if (parameters.disable_percent && input.contains('%'))
+ }
+ if (parameters.disable_percent && input.contains(QLatin1Char{'%'})) {
return Invalid;
- if (parameters.disable_slash && (input.contains('/') || input.contains('\\')))
+ }
+ if (parameters.disable_slash &&
+ (input.contains(QLatin1Char{'/'}) || input.contains(QLatin1Char{'\\'}))) {
return Invalid;
+ }
if (parameters.disable_number &&
std::any_of(input.begin(), input.end(), [](QChar c) { return c.isDigit(); })) {
return Invalid;
}
- if (parameters.disable_download_code &&
- std::any_of(input.begin(), input.end(), [](QChar c) { return c == 'O' || c == 'I'; })) {
+ if (parameters.disable_download_code && std::any_of(input.begin(), input.end(), [](QChar c) {
+ return c == QLatin1Char{'O'} || c == QLatin1Char{'I'};
+ })) {
return Invalid;
}
@@ -75,13 +82,13 @@ QtSoftwareKeyboardDialog::QtSoftwareKeyboardDialog(
length_label->setText(QStringLiteral("%1/%2").arg(text.size()).arg(parameters.max_length));
});
- buttons = new QDialogButtonBox;
- buttons->addButton(tr("Cancel"), QDialogButtonBox::RejectRole);
- buttons->addButton(parameters.submit_text.empty()
- ? tr("OK")
- : QString::fromStdU16String(parameters.submit_text),
- QDialogButtonBox::AcceptRole);
-
+ buttons = new QDialogButtonBox(QDialogButtonBox::Cancel);
+ if (parameters.submit_text.empty()) {
+ buttons->addButton(QDialogButtonBox::Ok);
+ } else {
+ buttons->addButton(QString::fromStdU16String(parameters.submit_text),
+ QDialogButtonBox::AcceptRole);
+ }
connect(buttons, &QDialogButtonBox::accepted, this, &QtSoftwareKeyboardDialog::accept);
connect(buttons, &QDialogButtonBox::rejected, this, &QtSoftwareKeyboardDialog::reject);
layout->addWidget(header_label);
@@ -97,13 +104,11 @@ QtSoftwareKeyboardDialog::QtSoftwareKeyboardDialog(
QtSoftwareKeyboardDialog::~QtSoftwareKeyboardDialog() = default;
void QtSoftwareKeyboardDialog::accept() {
- ok = true;
text = line_edit->text().toStdU16String();
QDialog::accept();
}
void QtSoftwareKeyboardDialog::reject() {
- ok = false;
text.clear();
QDialog::reject();
}
@@ -112,10 +117,6 @@ std::u16string QtSoftwareKeyboardDialog::GetText() const {
return text;
}
-bool QtSoftwareKeyboardDialog::GetStatus() const {
- return ok;
-}
-
QtSoftwareKeyboard::QtSoftwareKeyboard(GMainWindow& main_window) {
connect(this, &QtSoftwareKeyboard::MainWindowGetText, &main_window,
&GMainWindow::SoftwareKeyboardGetText, Qt::QueuedConnection);
@@ -141,12 +142,12 @@ void QtSoftwareKeyboard::SendTextCheckDialog(std::u16string error_message,
void QtSoftwareKeyboard::MainWindowFinishedText(std::optional<std::u16string> text) {
// Acquire the HLE mutex
- std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
- text_output(text);
+ std::lock_guard lock{HLE::g_hle_lock};
+ text_output(std::move(text));
}
void QtSoftwareKeyboard::MainWindowFinishedCheckDialog() {
// Acquire the HLE mutex
- std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
+ std::lock_guard lock{HLE::g_hle_lock};
finished_check();
}
diff --git a/src/yuzu/applets/software_keyboard.h b/src/yuzu/applets/software_keyboard.h
index c63720ba4..44bcece75 100644
--- a/src/yuzu/applets/software_keyboard.h
+++ b/src/yuzu/applets/software_keyboard.h
@@ -6,7 +6,6 @@
#include <QDialog>
#include <QValidator>
-#include "common/assert.h"
#include "core/frontend/applets/software_keyboard.h"
class GMainWindow;
@@ -37,10 +36,8 @@ public:
void reject() override;
std::u16string GetText() const;
- bool GetStatus() const;
private:
- bool ok = false;
std::u16string text;
QDialogButtonBox* buttons;
diff --git a/src/yuzu/applets/web_browser.cpp b/src/yuzu/applets/web_browser.cpp
index 6a9138d53..ac80b2fa2 100644
--- a/src/yuzu/applets/web_browser.cpp
+++ b/src/yuzu/applets/web_browser.cpp
@@ -56,6 +56,8 @@ constexpr char NX_SHIM_INJECT_SCRIPT[] = R"(
window.nx.endApplet = function() {
applet_done = true;
};
+
+ window.onkeypress = function(e) { if (e.keyCode === 13) { applet_done = true; } };
)";
QString GetNXShimInjectionScript() {
@@ -102,12 +104,12 @@ void QtWebBrowser::OpenPage(std::string_view url, std::function<void()> unpack_r
void QtWebBrowser::MainWindowUnpackRomFS() {
// Acquire the HLE mutex
- std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
+ std::lock_guard lock{HLE::g_hle_lock};
unpack_romfs_callback();
}
void QtWebBrowser::MainWindowFinishedBrowsing() {
// Acquire the HLE mutex
- std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
+ std::lock_guard lock{HLE::g_hle_lock};
finished_callback();
}
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp
index 3b070bfbb..07a720494 100644
--- a/src/yuzu/bootmanager.cpp
+++ b/src/yuzu/bootmanager.cpp
@@ -1,6 +1,13 @@
+// Copyright 2014 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
#include <QApplication>
#include <QHBoxLayout>
#include <QKeyEvent>
+#include <QOffscreenSurface>
+#include <QOpenGLWindow>
+#include <QPainter>
#include <QScreen>
#include <QWindow>
#include <fmt/format.h>
@@ -19,13 +26,13 @@
EmuThread::EmuThread(GRenderWindow* render_window) : render_window(render_window) {}
+EmuThread::~EmuThread() = default;
+
void EmuThread::run() {
render_window->MakeCurrent();
MicroProfileOnThreadCreate("EmuThread");
- stop_run = false;
-
emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
Core::System::GetInstance().Renderer().Rasterizer().LoadDiskResources(
@@ -40,7 +47,7 @@ void EmuThread::run() {
render_window->DoneCurrent();
}
- // holds whether the cpu was running during the last iteration,
+ // Holds whether the cpu was running during the last iteration,
// so that the DebugModeLeft signal can be emitted before the
// next execution step
bool was_active = false;
@@ -69,7 +76,7 @@ void EmuThread::run() {
was_active = false;
} else {
- std::unique_lock<std::mutex> lock(running_mutex);
+ std::unique_lock lock{running_mutex};
running_cv.wait(lock, [this] { return IsRunning() || exec_step || stop_run; });
}
}
@@ -84,13 +91,36 @@ void EmuThread::run() {
render_window->moveContext();
}
+class GGLContext : public Core::Frontend::GraphicsContext {
+public:
+ explicit GGLContext(QOpenGLContext* shared_context) : shared_context{shared_context} {
+ context.setFormat(shared_context->format());
+ context.setShareContext(shared_context);
+ context.create();
+ }
+
+ void MakeCurrent() override {
+ context.makeCurrent(shared_context->surface());
+ }
+
+ void DoneCurrent() override {
+ context.doneCurrent();
+ }
+
+ void SwapBuffers() override {}
+
+private:
+ QOpenGLContext* shared_context;
+ QOpenGLContext context;
+};
+
// This class overrides paintEvent and resizeEvent to prevent the GUI thread from stealing GL
// context.
// The corresponding functionality is handled in EmuThread instead
-class GGLWidgetInternal : public QGLWidget {
+class GGLWidgetInternal : public QOpenGLWindow {
public:
- GGLWidgetInternal(QGLFormat fmt, GRenderWindow* parent)
- : QGLWidget(fmt, parent), parent(parent) {}
+ GGLWidgetInternal(GRenderWindow* parent, QOpenGLContext* shared_context)
+ : QOpenGLWindow(shared_context), parent(parent) {}
void paintEvent(QPaintEvent* ev) override {
if (do_painting) {
@@ -103,9 +133,51 @@ public:
parent->OnFramebufferSizeChanged();
}
+ void keyPressEvent(QKeyEvent* event) override {
+ InputCommon::GetKeyboard()->PressKey(event->key());
+ }
+
+ void keyReleaseEvent(QKeyEvent* event) override {
+ InputCommon::GetKeyboard()->ReleaseKey(event->key());
+ }
+
+ void mousePressEvent(QMouseEvent* event) override {
+ if (event->source() == Qt::MouseEventSynthesizedBySystem)
+ return; // touch input is handled in TouchBeginEvent
+
+ const auto pos{event->pos()};
+ if (event->button() == Qt::LeftButton) {
+ const auto [x, y] = parent->ScaleTouch(pos);
+ parent->TouchPressed(x, y);
+ } else if (event->button() == Qt::RightButton) {
+ InputCommon::GetMotionEmu()->BeginTilt(pos.x(), pos.y());
+ }
+ }
+
+ void mouseMoveEvent(QMouseEvent* event) override {
+ if (event->source() == Qt::MouseEventSynthesizedBySystem)
+ return; // touch input is handled in TouchUpdateEvent
+
+ const auto pos{event->pos()};
+ const auto [x, y] = parent->ScaleTouch(pos);
+ parent->TouchMoved(x, y);
+ InputCommon::GetMotionEmu()->Tilt(pos.x(), pos.y());
+ }
+
+ void mouseReleaseEvent(QMouseEvent* event) override {
+ if (event->source() == Qt::MouseEventSynthesizedBySystem)
+ return; // touch input is handled in TouchEndEvent
+
+ if (event->button() == Qt::LeftButton)
+ parent->TouchReleased();
+ else if (event->button() == Qt::RightButton)
+ InputCommon::GetMotionEmu()->EndTilt();
+ }
+
void DisablePainting() {
do_painting = false;
}
+
void EnablePainting() {
do_painting = true;
}
@@ -115,17 +187,16 @@ private:
bool do_painting;
};
-GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread)
- : QWidget(parent), child(nullptr), emu_thread(emu_thread) {
-
+GRenderWindow::GRenderWindow(GMainWindow* parent, EmuThread* emu_thread)
+ : QWidget(parent), emu_thread(emu_thread) {
setWindowTitle(QStringLiteral("yuzu %1 | %2-%3")
- .arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc));
+ .arg(QString::fromUtf8(Common::g_build_name),
+ QString::fromUtf8(Common::g_scm_branch),
+ QString::fromUtf8(Common::g_scm_desc)));
setAttribute(Qt::WA_AcceptTouchEvents);
InputCommon::Init();
- InputCommon::StartJoystickEventHandler();
- connect(this, &GRenderWindow::FirstFrameDisplayed, static_cast<GMainWindow*>(parent),
- &GMainWindow::OnLoadComplete);
+ connect(this, &GRenderWindow::FirstFrameDisplayed, parent, &GMainWindow::OnLoadComplete);
}
GRenderWindow::~GRenderWindow() {
@@ -140,19 +211,19 @@ void GRenderWindow::moveContext() {
auto thread = (QThread::currentThread() == qApp->thread() && emu_thread != nullptr)
? emu_thread
: qApp->thread();
- child->context()->moveToThread(thread);
+ context->moveToThread(thread);
}
void GRenderWindow::SwapBuffers() {
- // In our multi-threaded QGLWidget use case we shouldn't need to call `makeCurrent`,
+ // In our multi-threaded QWidget use case we shouldn't need to call `makeCurrent`,
// since we never call `doneCurrent` in this thread.
// However:
// - The Qt debug runtime prints a bogus warning on the console if `makeCurrent` wasn't called
// since the last time `swapBuffers` was executed;
- // - On macOS, if `makeCurrent` isn't called explicitely, resizing the buffer breaks.
- child->makeCurrent();
+ // - On macOS, if `makeCurrent` isn't called explicitly, resizing the buffer breaks.
+ context->makeCurrent(child);
- child->swapBuffers();
+ context->swapBuffers(child);
if (!first_frame) {
emit FirstFrameDisplayed();
first_frame = true;
@@ -160,11 +231,11 @@ void GRenderWindow::SwapBuffers() {
}
void GRenderWindow::MakeCurrent() {
- child->makeCurrent();
+ context->makeCurrent(child);
}
void GRenderWindow::DoneCurrent() {
- child->doneCurrent();
+ context->doneCurrent();
}
void GRenderWindow::PollEvents() {}
@@ -177,14 +248,26 @@ void GRenderWindow::PollEvents() {}
void GRenderWindow::OnFramebufferSizeChanged() {
// Screen changes potentially incur a change in screen DPI, hence we should update the
// framebuffer size
- qreal pixelRatio = windowPixelRatio();
- unsigned width = child->QPaintDevice::width() * pixelRatio;
- unsigned height = child->QPaintDevice::height() * pixelRatio;
+ const qreal pixel_ratio = GetWindowPixelRatio();
+ const u32 width = child->QPaintDevice::width() * pixel_ratio;
+ const u32 height = child->QPaintDevice::height() * pixel_ratio;
UpdateCurrentFramebufferLayout(width, height);
}
+void GRenderWindow::ForwardKeyPressEvent(QKeyEvent* event) {
+ if (child) {
+ child->keyPressEvent(event);
+ }
+}
+
+void GRenderWindow::ForwardKeyReleaseEvent(QKeyEvent* event) {
+ if (child) {
+ child->keyReleaseEvent(event);
+ }
+}
+
void GRenderWindow::BackupGeometry() {
- geometry = ((QGLWidget*)this)->saveGeometry();
+ geometry = QWidget::saveGeometry();
}
void GRenderWindow::RestoreGeometry() {
@@ -201,21 +284,22 @@ void GRenderWindow::restoreGeometry(const QByteArray& geometry) {
QByteArray GRenderWindow::saveGeometry() {
// If we are a top-level widget, store the current geometry
// otherwise, store the last backup
- if (parent() == nullptr)
- return ((QGLWidget*)this)->saveGeometry();
- else
- return geometry;
+ if (parent() == nullptr) {
+ return QWidget::saveGeometry();
+ }
+
+ return geometry;
}
-qreal GRenderWindow::windowPixelRatio() const {
+qreal GRenderWindow::GetWindowPixelRatio() const {
// windowHandle() might not be accessible until the window is displayed to screen.
return windowHandle() ? windowHandle()->screen()->devicePixelRatio() : 1.0f;
}
-std::pair<unsigned, unsigned> GRenderWindow::ScaleTouch(const QPointF pos) const {
- const qreal pixel_ratio = windowPixelRatio();
- return {static_cast<unsigned>(std::max(std::round(pos.x() * pixel_ratio), qreal{0.0})),
- static_cast<unsigned>(std::max(std::round(pos.y() * pixel_ratio), qreal{0.0}))};
+std::pair<u32, u32> GRenderWindow::ScaleTouch(const QPointF pos) const {
+ const qreal pixel_ratio = GetWindowPixelRatio();
+ return {static_cast<u32>(std::max(std::round(pos.x() * pixel_ratio), qreal{0.0})),
+ static_cast<u32>(std::max(std::round(pos.y() * pixel_ratio), qreal{0.0}))};
}
void GRenderWindow::closeEvent(QCloseEvent* event) {
@@ -223,47 +307,6 @@ void GRenderWindow::closeEvent(QCloseEvent* event) {
QWidget::closeEvent(event);
}
-void GRenderWindow::keyPressEvent(QKeyEvent* event) {
- InputCommon::GetKeyboard()->PressKey(event->key());
-}
-
-void GRenderWindow::keyReleaseEvent(QKeyEvent* event) {
- InputCommon::GetKeyboard()->ReleaseKey(event->key());
-}
-
-void GRenderWindow::mousePressEvent(QMouseEvent* event) {
- if (event->source() == Qt::MouseEventSynthesizedBySystem)
- return; // touch input is handled in TouchBeginEvent
-
- auto pos = event->pos();
- if (event->button() == Qt::LeftButton) {
- const auto [x, y] = ScaleTouch(pos);
- this->TouchPressed(x, y);
- } else if (event->button() == Qt::RightButton) {
- InputCommon::GetMotionEmu()->BeginTilt(pos.x(), pos.y());
- }
-}
-
-void GRenderWindow::mouseMoveEvent(QMouseEvent* event) {
- if (event->source() == Qt::MouseEventSynthesizedBySystem)
- return; // touch input is handled in TouchUpdateEvent
-
- auto pos = event->pos();
- const auto [x, y] = ScaleTouch(pos);
- this->TouchMoved(x, y);
- InputCommon::GetMotionEmu()->Tilt(pos.x(), pos.y());
-}
-
-void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) {
- if (event->source() == Qt::MouseEventSynthesizedBySystem)
- return; // touch input is handled in TouchEndEvent
-
- if (event->button() == Qt::LeftButton)
- this->TouchReleased();
- else if (event->button() == Qt::RightButton)
- InputCommon::GetMotionEmu()->EndTilt();
-}
-
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());
@@ -312,39 +355,65 @@ void GRenderWindow::focusOutEvent(QFocusEvent* event) {
InputCommon::GetKeyboard()->ReleaseAllKeys();
}
-void GRenderWindow::OnClientAreaResized(unsigned width, unsigned height) {
+void GRenderWindow::OnClientAreaResized(u32 width, u32 height) {
NotifyClientAreaSizeChanged(std::make_pair(width, height));
}
+std::unique_ptr<Core::Frontend::GraphicsContext> GRenderWindow::CreateSharedContext() const {
+ return std::make_unique<GGLContext>(context.get());
+}
+
void GRenderWindow::InitRenderTarget() {
- if (child) {
- delete child;
- }
+ shared_context.reset();
+ context.reset();
- if (layout()) {
- delete layout();
- }
+ delete child;
+ child = nullptr;
+
+ delete container;
+ container = nullptr;
+
+ delete layout();
first_frame = false;
// TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground,
// WA_DontShowOnScreen, WA_DeleteOnClose
- QGLFormat fmt;
+ QSurfaceFormat fmt;
fmt.setVersion(4, 3);
- fmt.setProfile(QGLFormat::CoreProfile);
- fmt.setSwapInterval(false);
+ fmt.setProfile(QSurfaceFormat::CompatibilityProfile);
+ fmt.setOption(QSurfaceFormat::FormatOption::DeprecatedFunctions);
+ // TODO: expose a setting for buffer value (ie default/single/double/triple)
+ fmt.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior);
+ shared_context = std::make_unique<QOpenGLContext>();
+ shared_context->setFormat(fmt);
+ shared_context->create();
+ context = std::make_unique<QOpenGLContext>();
+ context->setShareContext(shared_context.get());
+ context->setFormat(fmt);
+ context->create();
+ fmt.setSwapInterval(0);
+
+ child = new GGLWidgetInternal(this, shared_context.get());
+ container = QWidget::createWindowContainer(child, this);
- // Requests a forward-compatible context, which is required to get a 3.2+ context on OS X
- fmt.setOption(QGL::NoDeprecatedFunctions);
-
- child = new GGLWidgetInternal(fmt, this);
QBoxLayout* layout = new QHBoxLayout(this);
-
- resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height);
- layout->addWidget(child);
+ layout->addWidget(container);
layout->setMargin(0);
setLayout(layout);
+ // Reset minimum size to avoid unwanted resizes when this function is called for a second time.
+ setMinimumSize(1, 1);
+
+ // Show causes the window to actually be created and the OpenGL context as well, but we don't
+ // want the widget to be shown yet, so immediately hide it.
+ show();
+ hide();
+
+ resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height);
+ child->resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height);
+ container->resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height);
+
OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
OnFramebufferSizeChanged();
@@ -353,24 +422,29 @@ void GRenderWindow::InitRenderTarget() {
BackupGeometry();
}
-void GRenderWindow::CaptureScreenshot(u16 res_scale, const QString& screenshot_path) {
+void GRenderWindow::CaptureScreenshot(u32 res_scale, const QString& screenshot_path) {
auto& renderer = Core::System::GetInstance().Renderer();
- if (!res_scale)
+ if (res_scale == 0) {
res_scale = VideoCore::GetResolutionScaleFactor(renderer);
+ }
const Layout::FramebufferLayout layout{Layout::FrameLayoutFromResolutionScale(res_scale)};
screenshot_image = QImage(QSize(layout.width, layout.height), QImage::Format_RGB32);
- renderer.RequestScreenshot(screenshot_image.bits(),
- [=] {
- screenshot_image.mirrored(false, true).save(screenshot_path);
- LOG_INFO(Frontend, "The screenshot is saved.");
- },
- layout);
+ renderer.RequestScreenshot(
+ screenshot_image.bits(),
+ [=] {
+ const std::string std_screenshot_path = screenshot_path.toStdString();
+ if (screenshot_image.mirrored(false, true).save(screenshot_path)) {
+ LOG_INFO(Frontend, "Screenshot saved to \"{}\"", std_screenshot_path);
+ } else {
+ LOG_ERROR(Frontend, "Failed to save screenshot to \"{}\"", std_screenshot_path);
+ }
+ },
+ layout);
}
-void GRenderWindow::OnMinimalClientAreaChangeRequest(
- const std::pair<unsigned, unsigned>& minimal_size) {
+void GRenderWindow::OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) {
setMinimumSize(minimal_size.first, minimal_size.second);
}
diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h
index 7226e690e..2fc64895f 100644
--- a/src/yuzu/bootmanager.h
+++ b/src/yuzu/bootmanager.h
@@ -7,10 +7,9 @@
#include <atomic>
#include <condition_variable>
#include <mutex>
-#include <QGLWidget>
#include <QImage>
#include <QThread>
-#include "common/thread.h"
+#include <QWidget>
#include "core/core.h"
#include "core/frontend/emu_window.h"
@@ -21,16 +20,19 @@ class QTouchEvent;
class GGLWidgetInternal;
class GMainWindow;
class GRenderWindow;
+class QSurface;
+class QOpenGLContext;
namespace VideoCore {
enum class LoadCallbackStage;
}
-class EmuThread : public QThread {
+class EmuThread final : public QThread {
Q_OBJECT
public:
explicit EmuThread(GRenderWindow* render_window);
+ ~EmuThread() override;
/**
* Start emulation (on new thread)
@@ -53,7 +55,7 @@ public:
* @note This function is thread-safe
*/
void SetRunning(bool running) {
- std::unique_lock<std::mutex> lock(running_mutex);
+ std::unique_lock lock{running_mutex};
this->running = running;
lock.unlock();
running_cv.notify_all();
@@ -113,7 +115,7 @@ class GRenderWindow : public QWidget, public Core::Frontend::EmuWindow {
Q_OBJECT
public:
- GRenderWindow(QWidget* parent, EmuThread* emu_thread);
+ GRenderWindow(GMainWindow* parent, EmuThread* emu_thread);
~GRenderWindow() override;
// EmuWindow implementation
@@ -121,32 +123,28 @@ public:
void MakeCurrent() override;
void DoneCurrent() override;
void PollEvents() override;
+ std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override;
+
+ void ForwardKeyPressEvent(QKeyEvent* event);
+ void ForwardKeyReleaseEvent(QKeyEvent* event);
void BackupGeometry();
void RestoreGeometry();
void restoreGeometry(const QByteArray& geometry); // overridden
QByteArray saveGeometry(); // overridden
- qreal windowPixelRatio() const;
+ qreal GetWindowPixelRatio() const;
+ std::pair<u32, u32> ScaleTouch(QPointF pos) const;
void closeEvent(QCloseEvent* event) override;
-
- void keyPressEvent(QKeyEvent* event) override;
- void keyReleaseEvent(QKeyEvent* event) override;
-
- void mousePressEvent(QMouseEvent* event) override;
- void mouseMoveEvent(QMouseEvent* event) override;
- void mouseReleaseEvent(QMouseEvent* event) override;
-
bool event(QEvent* event) override;
-
void focusOutEvent(QFocusEvent* event) override;
- void OnClientAreaResized(unsigned width, unsigned height);
+ void OnClientAreaResized(u32 width, u32 height);
void InitRenderTarget();
- void CaptureScreenshot(u16 res_scale, const QString& screenshot_path);
+ void CaptureScreenshot(u32 res_scale, const QString& screenshot_path);
public slots:
void moveContext(); // overridden
@@ -161,19 +159,23 @@ signals:
void FirstFrameDisplayed();
private:
- std::pair<unsigned, unsigned> ScaleTouch(const QPointF pos) const;
void TouchBeginEvent(const QTouchEvent* event);
void TouchUpdateEvent(const QTouchEvent* event);
void TouchEndEvent();
- void OnMinimalClientAreaChangeRequest(
- const std::pair<unsigned, unsigned>& minimal_size) override;
+ void OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) override;
- GGLWidgetInternal* child;
+ QWidget* container = nullptr;
+ GGLWidgetInternal* child = nullptr;
QByteArray geometry;
EmuThread* emu_thread;
+ // Context that backs the GGLWidgetInternal (and will be used by core to render)
+ std::unique_ptr<QOpenGLContext> context;
+ // Context that will be shared between all newly created contexts. This should never be made
+ // current
+ std::unique_ptr<QOpenGLContext> shared_context;
/// Temporary storage of the screenshot taken
QImage screenshot_image;
diff --git a/src/yuzu/compatdb.cpp b/src/yuzu/compatdb.cpp
index c8b0a5ec0..5477f050c 100644
--- a/src/yuzu/compatdb.cpp
+++ b/src/yuzu/compatdb.cpp
@@ -58,7 +58,7 @@ void CompatDB::Submit() {
button(NextButton)->setEnabled(false);
button(NextButton)->setText(tr("Submitting"));
- button(QWizard::CancelButton)->setVisible(false);
+ button(CancelButton)->setVisible(false);
testcase_watcher.setFuture(QtConcurrent::run(
[] { return Core::System::GetInstance().TelemetrySession().SubmitTestcase(); }));
@@ -74,12 +74,12 @@ void CompatDB::OnTestcaseSubmitted() {
tr("An error occured while sending the Testcase"));
button(NextButton)->setEnabled(true);
button(NextButton)->setText(tr("Next"));
- button(QWizard::CancelButton)->setVisible(true);
+ button(CancelButton)->setVisible(true);
} else {
next();
// older versions of QT don't support the "NoCancelButtonOnLastPage" option, this is a
// workaround
- button(QWizard::CancelButton)->setVisible(false);
+ button(CancelButton)->setVisible(false);
}
}
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 4650f96a3..10e5c5c38 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -2,6 +2,8 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <array>
+#include <QKeySequence>
#include <QSettings>
#include "common/file_util.h"
#include "configure_input_simple.h"
@@ -17,7 +19,6 @@ Config::Config() {
FileUtil::CreateFullPath(qt_config_loc);
qt_config =
std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), QSettings::IniFormat);
-
Reload();
}
@@ -205,59 +206,92 @@ const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> Config::default
Qt::Key_Control, Qt::Key_Shift, Qt::Key_AltGr, Qt::Key_ApplicationRight,
};
+// This shouldn't have anything except static initializers (no functions). So
+// QKeySequence(...).toString() is NOT ALLOWED HERE.
+// 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, 15> default_hotkeys{{
+ {QStringLiteral("Capture Screenshot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), Qt::ApplicationShortcut}},
+ {QStringLiteral("Continue/Pause Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F4"), Qt::WindowShortcut}},
+ {QStringLiteral("Decrease Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("-"), Qt::ApplicationShortcut}},
+ {QStringLiteral("Exit yuzu"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Q"), Qt::WindowShortcut}},
+ {QStringLiteral("Exit Fullscreen"), QStringLiteral("Main Window"), {QStringLiteral("Esc"), Qt::WindowShortcut}},
+ {QStringLiteral("Fullscreen"), QStringLiteral("Main Window"), {QStringLiteral("F11"), Qt::WindowShortcut}},
+ {QStringLiteral("Increase Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("+"), Qt::ApplicationShortcut}},
+ {QStringLiteral("Load Amiibo"), QStringLiteral("Main Window"), {QStringLiteral("F2"), Qt::ApplicationShortcut}},
+ {QStringLiteral("Load File"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+O"), Qt::WindowShortcut}},
+ {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 Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Z"), Qt::ApplicationShortcut}},
+ {QStringLiteral("Toggle Status Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+S"), Qt::WindowShortcut}},
+ {QStringLiteral("Change Docked Mode"), QStringLiteral("Main Window"), {QStringLiteral("F10"), Qt::ApplicationShortcut}},
+}};
+// clang-format on
+
void Config::ReadPlayerValues() {
for (std::size_t p = 0; p < Settings::values.players.size(); ++p) {
auto& player = Settings::values.players[p];
- player.connected = ReadSetting(QString("player_%1_connected").arg(p), false).toBool();
+ player.connected =
+ ReadSetting(QStringLiteral("player_%1_connected").arg(p), false).toBool();
player.type = static_cast<Settings::ControllerType>(
qt_config
- ->value(QString("player_%1_type").arg(p),
+ ->value(QStringLiteral("player_%1_type").arg(p),
static_cast<u8>(Settings::ControllerType::DualJoycon))
.toUInt());
player.body_color_left = qt_config
- ->value(QString("player_%1_body_color_left").arg(p),
+ ->value(QStringLiteral("player_%1_body_color_left").arg(p),
Settings::JOYCON_BODY_NEON_BLUE)
.toUInt();
player.body_color_right = qt_config
- ->value(QString("player_%1_body_color_right").arg(p),
+ ->value(QStringLiteral("player_%1_body_color_right").arg(p),
Settings::JOYCON_BODY_NEON_RED)
.toUInt();
player.button_color_left = qt_config
- ->value(QString("player_%1_button_color_left").arg(p),
+ ->value(QStringLiteral("player_%1_button_color_left").arg(p),
Settings::JOYCON_BUTTONS_NEON_BLUE)
.toUInt();
- player.button_color_right = qt_config
- ->value(QString("player_%1_button_color_right").arg(p),
- Settings::JOYCON_BUTTONS_NEON_RED)
- .toUInt();
+ player.button_color_right =
+ qt_config
+ ->value(QStringLiteral("player_%1_button_color_right").arg(p),
+ Settings::JOYCON_BUTTONS_NEON_RED)
+ .toUInt();
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
- std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
- player.buttons[i] =
- qt_config
- ->value(QString("player_%1_").arg(p) + Settings::NativeButton::mapping[i],
- QString::fromStdString(default_param))
- .toString()
- .toStdString();
- if (player.buttons[i].empty())
- player.buttons[i] = default_param;
+ 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::NativeAnalog::NumAnalogs; ++i) {
- std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
+ const 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);
- player.analogs[i] =
- qt_config
- ->value(QString("player_%1_").arg(p) + Settings::NativeAnalog::mapping[i],
- QString::fromStdString(default_param))
- .toString()
- .toStdString();
- if (player.analogs[i].empty())
- player.analogs[i] = default_param;
+ 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;
+ }
}
}
@@ -269,36 +303,45 @@ void Config::ReadPlayerValues() {
}
void Config::ReadDebugValues() {
- Settings::values.debug_pad_enabled = ReadSetting("debug_pad_enabled", false).toBool();
+ Settings::values.debug_pad_enabled =
+ ReadSetting(QStringLiteral("debug_pad_enabled"), false).toBool();
+
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
- std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
- Settings::values.debug_pad_buttons[i] =
- qt_config
- ->value(QString("debug_pad_") + Settings::NativeButton::mapping[i],
- QString::fromStdString(default_param))
- .toString()
- .toStdString();
- if (Settings::values.debug_pad_buttons[i].empty())
- Settings::values.debug_pad_buttons[i] = default_param;
+ const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
+ auto& debug_pad_buttons = Settings::values.debug_pad_buttons[i];
+
+ debug_pad_buttons = qt_config
+ ->value(QStringLiteral("debug_pad_") +
+ QString::fromUtf8(Settings::NativeButton::mapping[i]),
+ QString::fromStdString(default_param))
+ .toString()
+ .toStdString();
+ if (debug_pad_buttons.empty()) {
+ debug_pad_buttons = default_param;
+ }
}
for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
- std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
+ const 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.debug_pad_analogs[i] =
- qt_config
- ->value(QString("debug_pad_") + Settings::NativeAnalog::mapping[i],
- QString::fromStdString(default_param))
- .toString()
- .toStdString();
- if (Settings::values.debug_pad_analogs[i].empty())
- Settings::values.debug_pad_analogs[i] = default_param;
+ auto& debug_pad_analogs = Settings::values.debug_pad_analogs[i];
+
+ debug_pad_analogs = qt_config
+ ->value(QStringLiteral("debug_pad_") +
+ QString::fromUtf8(Settings::NativeAnalog::mapping[i]),
+ QString::fromStdString(default_param))
+ .toString()
+ .toStdString();
+ if (debug_pad_analogs.empty()) {
+ debug_pad_analogs = default_param;
+ }
}
}
void Config::ReadKeyboardValues() {
- Settings::values.keyboard_enabled = ReadSetting("keyboard_enabled", false).toBool();
+ Settings::values.keyboard_enabled =
+ ReadSetting(QStringLiteral("keyboard_enabled"), false).toBool();
std::transform(default_keyboard_keys.begin(), default_keyboard_keys.end(),
Settings::values.keyboard_keys.begin(), InputCommon::GenerateKeyboardParam);
@@ -311,31 +354,41 @@ void Config::ReadKeyboardValues() {
}
void Config::ReadMouseValues() {
- Settings::values.mouse_enabled = ReadSetting("mouse_enabled", false).toBool();
+ Settings::values.mouse_enabled = ReadSetting(QStringLiteral("mouse_enabled"), false).toBool();
for (int i = 0; i < Settings::NativeMouseButton::NumMouseButtons; ++i) {
- std::string default_param = InputCommon::GenerateKeyboardParam(default_mouse_buttons[i]);
- Settings::values.mouse_buttons[i] =
- qt_config
- ->value(QString("mouse_") + Settings::NativeMouseButton::mapping[i],
- QString::fromStdString(default_param))
- .toString()
- .toStdString();
- if (Settings::values.mouse_buttons[i].empty())
- Settings::values.mouse_buttons[i] = default_param;
+ const std::string default_param =
+ InputCommon::GenerateKeyboardParam(default_mouse_buttons[i]);
+ auto& mouse_buttons = Settings::values.mouse_buttons[i];
+
+ mouse_buttons = qt_config
+ ->value(QStringLiteral("mouse_") +
+ QString::fromUtf8(Settings::NativeMouseButton::mapping[i]),
+ QString::fromStdString(default_param))
+ .toString()
+ .toStdString();
+ if (mouse_buttons.empty()) {
+ mouse_buttons = default_param;
+ }
}
}
void Config::ReadTouchscreenValues() {
- Settings::values.touchscreen.enabled = ReadSetting("touchscreen_enabled", true).toBool();
+ Settings::values.touchscreen.enabled =
+ ReadSetting(QStringLiteral("touchscreen_enabled"), true).toBool();
Settings::values.touchscreen.device =
- ReadSetting("touchscreen_device", "engine:emu_window").toString().toStdString();
+ ReadSetting(QStringLiteral("touchscreen_device"), QStringLiteral("engine:emu_window"))
+ .toString()
+ .toStdString();
- Settings::values.touchscreen.finger = ReadSetting("touchscreen_finger", 0).toUInt();
- Settings::values.touchscreen.rotation_angle = ReadSetting("touchscreen_angle", 0).toUInt();
- Settings::values.touchscreen.diameter_x = ReadSetting("touchscreen_diameter_x", 15).toUInt();
- Settings::values.touchscreen.diameter_y = ReadSetting("touchscreen_diameter_y", 15).toUInt();
- qt_config->endGroup();
+ 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 =
+ ReadSetting(QStringLiteral("touchscreen_diameter_x"), 15).toUInt();
+ Settings::values.touchscreen.diameter_y =
+ ReadSetting(QStringLiteral("touchscreen_diameter_y"), 15).toUInt();
}
void Config::ApplyDefaultProfileIfInputInvalid() {
@@ -345,8 +398,25 @@ void Config::ApplyDefaultProfileIfInputInvalid() {
}
}
-void Config::ReadValues() {
- qt_config->beginGroup("Controls");
+void Config::ReadAudioValues() {
+ qt_config->beginGroup(QStringLiteral("Audio"));
+
+ Settings::values.sink_id = ReadSetting(QStringLiteral("output_engine"), QStringLiteral("auto"))
+ .toString()
+ .toStdString();
+ Settings::values.enable_audio_stretching =
+ ReadSetting(QStringLiteral("enable_audio_stretching"), true).toBool();
+ Settings::values.audio_device_id =
+ ReadSetting(QStringLiteral("output_device"), QStringLiteral("auto"))
+ .toString()
+ .toStdString();
+ Settings::values.volume = ReadSetting(QStringLiteral("volume"), 1).toFloat();
+
+ qt_config->endGroup();
+}
+
+void Config::ReadControlValues() {
+ qt_config->beginGroup(QStringLiteral("Controls"));
ReadPlayerValues();
ReadDebugValues();
@@ -355,223 +425,308 @@ void Config::ReadValues() {
ReadTouchscreenValues();
Settings::values.motion_device =
- ReadSetting("motion_device", "engine:motion_emu,update_period:100,sensitivity:0.01")
+ ReadSetting(QStringLiteral("motion_device"),
+ QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01"))
.toString()
.toStdString();
- qt_config->beginGroup("Core");
- Settings::values.use_cpu_jit = ReadSetting("use_cpu_jit", true).toBool();
- Settings::values.use_multi_core = ReadSetting("use_multi_core", false).toBool();
qt_config->endGroup();
+}
- qt_config->beginGroup("Renderer");
- Settings::values.resolution_factor = ReadSetting("resolution_factor", 1.0).toFloat();
- Settings::values.use_frame_limit = ReadSetting("use_frame_limit", true).toBool();
- Settings::values.frame_limit = ReadSetting("frame_limit", 100).toInt();
- Settings::values.use_disk_shader_cache = ReadSetting("use_disk_shader_cache", true).toBool();
- Settings::values.use_accurate_gpu_emulation =
- ReadSetting("use_accurate_gpu_emulation", false).toBool();
- Settings::values.use_asynchronous_gpu_emulation =
- ReadSetting("use_asynchronous_gpu_emulation", false).toBool();
+void Config::ReadCoreValues() {
+ qt_config->beginGroup(QStringLiteral("Core"));
- Settings::values.bg_red = ReadSetting("bg_red", 0.0).toFloat();
- Settings::values.bg_green = ReadSetting("bg_green", 0.0).toFloat();
- Settings::values.bg_blue = ReadSetting("bg_blue", 0.0).toFloat();
- qt_config->endGroup();
+ Settings::values.use_cpu_jit = ReadSetting(QStringLiteral("use_cpu_jit"), true).toBool();
+ Settings::values.use_multi_core = ReadSetting(QStringLiteral("use_multi_core"), false).toBool();
- qt_config->beginGroup("Audio");
- Settings::values.sink_id = ReadSetting("output_engine", "auto").toString().toStdString();
- Settings::values.enable_audio_stretching =
- ReadSetting("enable_audio_stretching", true).toBool();
- Settings::values.audio_device_id =
- ReadSetting("output_device", "auto").toString().toStdString();
- Settings::values.volume = ReadSetting("volume", 1).toFloat();
qt_config->endGroup();
+}
+
+void Config::ReadDataStorageValues() {
+ qt_config->beginGroup(QStringLiteral("Data Storage"));
- qt_config->beginGroup("Data Storage");
- Settings::values.use_virtual_sd = ReadSetting("use_virtual_sd", true).toBool();
+ Settings::values.use_virtual_sd = ReadSetting(QStringLiteral("use_virtual_sd"), true).toBool();
FileUtil::GetUserPath(
FileUtil::UserPath::NANDDir,
qt_config
- ->value("nand_directory",
+ ->value(QStringLiteral("nand_directory"),
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)))
.toString()
.toStdString());
FileUtil::GetUserPath(
FileUtil::UserPath::SDMCDir,
qt_config
- ->value("sdmc_directory",
+ ->value(QStringLiteral("sdmc_directory"),
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)))
.toString()
.toStdString());
+
qt_config->endGroup();
+}
+
+void Config::ReadDebuggingValues() {
+ qt_config->beginGroup(QStringLiteral("Debugging"));
+
+ 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"), QStringLiteral("")).toString().toStdString();
+ Settings::values.dump_exefs = ReadSetting(QStringLiteral("dump_exefs"), false).toBool();
+ Settings::values.dump_nso = ReadSetting(QStringLiteral("dump_nso"), false).toBool();
- qt_config->beginGroup("Core");
- Settings::values.use_cpu_jit = ReadSetting("use_cpu_jit", true).toBool();
- Settings::values.use_multi_core = ReadSetting("use_multi_core", false).toBool();
qt_config->endGroup();
+}
- qt_config->beginGroup("System");
- Settings::values.use_docked_mode = ReadSetting("use_docked_mode", false).toBool();
- Settings::values.enable_nfc = ReadSetting("enable_nfc", true).toBool();
+void Config::ReadDisabledAddOnValues() {
+ const auto size = qt_config->beginReadArray(QStringLiteral("DisabledAddOns"));
- Settings::values.current_user =
- std::clamp<int>(ReadSetting("current_user", 0).toInt(), 0, Service::Account::MAX_USERS - 1);
+ for (int i = 0; i < size; ++i) {
+ qt_config->setArrayIndex(i);
+ const auto title_id = ReadSetting(QStringLiteral("title_id"), 0).toULongLong();
+ std::vector<std::string> out;
+ const auto d_size = qt_config->beginReadArray(QStringLiteral("disabled"));
+ for (int j = 0; j < d_size; ++j) {
+ qt_config->setArrayIndex(j);
+ out.push_back(
+ ReadSetting(QStringLiteral("d"), QStringLiteral("")).toString().toStdString());
+ }
+ qt_config->endArray();
+ Settings::values.disabled_addons.insert_or_assign(title_id, out);
+ }
+
+ qt_config->endArray();
+}
- Settings::values.language_index = ReadSetting("language_index", 1).toInt();
+void Config::ReadMiscellaneousValues() {
+ qt_config->beginGroup(QStringLiteral("Miscellaneous"));
- const auto rng_seed_enabled = ReadSetting("rng_seed_enabled", false).toBool();
+ Settings::values.log_filter =
+ ReadSetting(QStringLiteral("log_filter"), QStringLiteral("*:Info"))
+ .toString()
+ .toStdString();
+ Settings::values.use_dev_keys = ReadSetting(QStringLiteral("use_dev_keys"), false).toBool();
+
+ qt_config->endGroup();
+}
+
+void Config::ReadPathValues() {
+ qt_config->beginGroup(QStringLiteral("Paths"));
+
+ UISettings::values.roms_path = ReadSetting(QStringLiteral("romsPath")).toString();
+ UISettings::values.symbols_path = ReadSetting(QStringLiteral("symbolsPath")).toString();
+ UISettings::values.game_directory_path =
+ ReadSetting(QStringLiteral("gameListRootDir"), QStringLiteral(".")).toString();
+ UISettings::values.game_directory_deepscan =
+ ReadSetting(QStringLiteral("gameListDeepScan"), false).toBool();
+ UISettings::values.recent_files = ReadSetting(QStringLiteral("recentFiles")).toStringList();
+
+ qt_config->endGroup();
+}
+
+void Config::ReadRendererValues() {
+ qt_config->beginGroup(QStringLiteral("Renderer"));
+
+ Settings::values.resolution_factor =
+ ReadSetting(QStringLiteral("resolution_factor"), 1.0).toFloat();
+ Settings::values.use_frame_limit =
+ ReadSetting(QStringLiteral("use_frame_limit"), true).toBool();
+ Settings::values.frame_limit = ReadSetting(QStringLiteral("frame_limit"), 100).toInt();
+ Settings::values.use_disk_shader_cache =
+ ReadSetting(QStringLiteral("use_disk_shader_cache"), true).toBool();
+ Settings::values.use_accurate_gpu_emulation =
+ ReadSetting(QStringLiteral("use_accurate_gpu_emulation"), false).toBool();
+ Settings::values.use_asynchronous_gpu_emulation =
+ ReadSetting(QStringLiteral("use_asynchronous_gpu_emulation"), false).toBool();
+ Settings::values.force_30fps_mode =
+ ReadSetting(QStringLiteral("force_30fps_mode"), false).toBool();
+
+ Settings::values.bg_red = ReadSetting(QStringLiteral("bg_red"), 0.0).toFloat();
+ Settings::values.bg_green = ReadSetting(QStringLiteral("bg_green"), 0.0).toFloat();
+ Settings::values.bg_blue = ReadSetting(QStringLiteral("bg_blue"), 0.0).toFloat();
+
+ qt_config->endGroup();
+}
+
+void Config::ReadShortcutValues() {
+ qt_config->beginGroup(QStringLiteral("Shortcuts"));
+
+ for (const auto& [name, group, shortcut] : default_hotkeys) {
+ const auto& [keyseq, context] = shortcut;
+ qt_config->beginGroup(group);
+ qt_config->beginGroup(name);
+ UISettings::values.shortcuts.push_back(
+ {name,
+ group,
+ {ReadSetting(QStringLiteral("KeySeq"), keyseq).toString(),
+ ReadSetting(QStringLiteral("Context"), context).toInt()}});
+ qt_config->endGroup();
+ qt_config->endGroup();
+ }
+
+ qt_config->endGroup();
+}
+
+void Config::ReadSystemValues() {
+ qt_config->beginGroup(QStringLiteral("System"));
+
+ Settings::values.use_docked_mode =
+ ReadSetting(QStringLiteral("use_docked_mode"), false).toBool();
+
+ Settings::values.current_user = std::clamp<int>(
+ ReadSetting(QStringLiteral("current_user"), 0).toInt(), 0, Service::Account::MAX_USERS - 1);
+
+ Settings::values.language_index = ReadSetting(QStringLiteral("language_index"), 1).toInt();
+
+ const auto rng_seed_enabled = ReadSetting(QStringLiteral("rng_seed_enabled"), false).toBool();
if (rng_seed_enabled) {
- Settings::values.rng_seed = ReadSetting("rng_seed", 0).toULongLong();
+ Settings::values.rng_seed = ReadSetting(QStringLiteral("rng_seed"), 0).toULongLong();
} else {
Settings::values.rng_seed = std::nullopt;
}
- const auto custom_rtc_enabled = ReadSetting("custom_rtc_enabled", false).toBool();
+ const auto custom_rtc_enabled =
+ ReadSetting(QStringLiteral("custom_rtc_enabled"), false).toBool();
if (custom_rtc_enabled) {
Settings::values.custom_rtc =
- std::chrono::seconds(ReadSetting("custom_rtc", 0).toULongLong());
+ std::chrono::seconds(ReadSetting(QStringLiteral("custom_rtc"), 0).toULongLong());
} else {
Settings::values.custom_rtc = std::nullopt;
}
qt_config->endGroup();
+}
- qt_config->beginGroup("Miscellaneous");
- Settings::values.log_filter = ReadSetting("log_filter", "*:Info").toString().toStdString();
- Settings::values.use_dev_keys = ReadSetting("use_dev_keys", false).toBool();
- qt_config->endGroup();
+void Config::ReadUIValues() {
+ qt_config->beginGroup(QStringLiteral("UI"));
- qt_config->beginGroup("Debugging");
- Settings::values.use_gdbstub = ReadSetting("use_gdbstub", false).toBool();
- Settings::values.gdbstub_port = ReadSetting("gdbstub_port", 24689).toInt();
- Settings::values.program_args = ReadSetting("program_args", "").toString().toStdString();
- Settings::values.dump_exefs = ReadSetting("dump_exefs", false).toBool();
- Settings::values.dump_nso = ReadSetting("dump_nso", false).toBool();
- qt_config->endGroup();
+ UISettings::values.theme =
+ ReadSetting(QStringLiteral("theme"), QString::fromUtf8(UISettings::themes[0].second))
+ .toString();
+ UISettings::values.enable_discord_presence =
+ ReadSetting(QStringLiteral("enable_discord_presence"), true).toBool();
+ UISettings::values.screenshot_resolution_factor =
+ static_cast<u16>(ReadSetting(QStringLiteral("screenshot_resolution_factor"), 0).toUInt());
+ UISettings::values.select_user_on_boot =
+ ReadSetting(QStringLiteral("select_user_on_boot"), false).toBool();
+
+ ReadUIGamelistValues();
+ ReadUILayoutValues();
+ ReadPathValues();
+ ReadShortcutValues();
+
+ UISettings::values.single_window_mode =
+ ReadSetting(QStringLiteral("singleWindowMode"), true).toBool();
+ UISettings::values.fullscreen = ReadSetting(QStringLiteral("fullscreen"), false).toBool();
+ UISettings::values.display_titlebar =
+ ReadSetting(QStringLiteral("displayTitleBars"), true).toBool();
+ UISettings::values.show_filter_bar =
+ ReadSetting(QStringLiteral("showFilterBar"), true).toBool();
+ UISettings::values.show_status_bar =
+ ReadSetting(QStringLiteral("showStatusBar"), true).toBool();
+ UISettings::values.confirm_before_closing =
+ ReadSetting(QStringLiteral("confirmClose"), true).toBool();
+ UISettings::values.first_start = ReadSetting(QStringLiteral("firstStart"), true).toBool();
+ UISettings::values.callout_flags = ReadSetting(QStringLiteral("calloutFlags"), 0).toUInt();
+ UISettings::values.show_console = ReadSetting(QStringLiteral("showConsole"), false).toBool();
+ UISettings::values.profile_index = ReadSetting(QStringLiteral("profileIndex"), 0).toUInt();
+
+ ApplyDefaultProfileIfInputInvalid();
- qt_config->beginGroup("WebService");
- Settings::values.enable_telemetry = ReadSetting("enable_telemetry", true).toBool();
- Settings::values.web_api_url =
- ReadSetting("web_api_url", "https://api.yuzu-emu.org").toString().toStdString();
- Settings::values.yuzu_username = ReadSetting("yuzu_username").toString().toStdString();
- Settings::values.yuzu_token = ReadSetting("yuzu_token").toString().toStdString();
qt_config->endGroup();
+}
- const auto size = qt_config->beginReadArray("DisabledAddOns");
- for (int i = 0; i < size; ++i) {
- qt_config->setArrayIndex(i);
- const auto title_id = ReadSetting("title_id", 0).toULongLong();
- std::vector<std::string> out;
- const auto d_size = qt_config->beginReadArray("disabled");
- for (int j = 0; j < d_size; ++j) {
- qt_config->setArrayIndex(j);
- out.push_back(ReadSetting("d", "").toString().toStdString());
- }
- qt_config->endArray();
- Settings::values.disabled_addons.insert_or_assign(title_id, out);
- }
- qt_config->endArray();
+void Config::ReadUIGamelistValues() {
+ qt_config->beginGroup(QStringLiteral("UIGameList"));
+
+ UISettings::values.show_unknown = ReadSetting(QStringLiteral("show_unknown"), true).toBool();
+ UISettings::values.show_add_ons = ReadSetting(QStringLiteral("show_add_ons"), true).toBool();
+ UISettings::values.icon_size = ReadSetting(QStringLiteral("icon_size"), 64).toUInt();
+ UISettings::values.row_1_text_id = ReadSetting(QStringLiteral("row_1_text_id"), 3).toUInt();
+ UISettings::values.row_2_text_id = ReadSetting(QStringLiteral("row_2_text_id"), 2).toUInt();
+ UISettings::values.cache_game_list =
+ ReadSetting(QStringLiteral("cache_game_list"), true).toBool();
- qt_config->beginGroup("UI");
- UISettings::values.theme = ReadSetting("theme", UISettings::themes[0].second).toString();
- UISettings::values.enable_discord_presence =
- ReadSetting("enable_discord_presence", true).toBool();
- UISettings::values.screenshot_resolution_factor =
- static_cast<u16>(ReadSetting("screenshot_resolution_factor", 0).toUInt());
- UISettings::values.select_user_on_boot = ReadSetting("select_user_on_boot", false).toBool();
-
- qt_config->beginGroup("UIGameList");
- UISettings::values.show_unknown = ReadSetting("show_unknown", true).toBool();
- UISettings::values.show_add_ons = ReadSetting("show_add_ons", true).toBool();
- UISettings::values.icon_size = ReadSetting("icon_size", 64).toUInt();
- UISettings::values.row_1_text_id = ReadSetting("row_1_text_id", 3).toUInt();
- UISettings::values.row_2_text_id = ReadSetting("row_2_text_id", 2).toUInt();
qt_config->endGroup();
+}
+
+void Config::ReadUILayoutValues() {
+ qt_config->beginGroup(QStringLiteral("UILayout"));
- qt_config->beginGroup("UILayout");
- UISettings::values.geometry = ReadSetting("geometry").toByteArray();
- UISettings::values.state = ReadSetting("state").toByteArray();
- UISettings::values.renderwindow_geometry = ReadSetting("geometryRenderWindow").toByteArray();
- UISettings::values.gamelist_header_state = ReadSetting("gameListHeaderState").toByteArray();
+ UISettings::values.geometry = ReadSetting(QStringLiteral("geometry")).toByteArray();
+ UISettings::values.state = ReadSetting(QStringLiteral("state")).toByteArray();
+ UISettings::values.renderwindow_geometry =
+ ReadSetting(QStringLiteral("geometryRenderWindow")).toByteArray();
+ UISettings::values.gamelist_header_state =
+ ReadSetting(QStringLiteral("gameListHeaderState")).toByteArray();
UISettings::values.microprofile_geometry =
- ReadSetting("microProfileDialogGeometry").toByteArray();
+ ReadSetting(QStringLiteral("microProfileDialogGeometry")).toByteArray();
UISettings::values.microprofile_visible =
- ReadSetting("microProfileDialogVisible", false).toBool();
- qt_config->endGroup();
+ ReadSetting(QStringLiteral("microProfileDialogVisible"), false).toBool();
- qt_config->beginGroup("Paths");
- UISettings::values.roms_path = ReadSetting("romsPath").toString();
- UISettings::values.symbols_path = ReadSetting("symbolsPath").toString();
- UISettings::values.gamedir = ReadSetting("gameListRootDir", ".").toString();
- UISettings::values.gamedir_deepscan = ReadSetting("gameListDeepScan", false).toBool();
- UISettings::values.recent_files = ReadSetting("recentFiles").toStringList();
qt_config->endGroup();
+}
- qt_config->beginGroup("Shortcuts");
- QStringList groups = qt_config->childGroups();
- for (auto group : groups) {
- qt_config->beginGroup(group);
+void Config::ReadWebServiceValues() {
+ qt_config->beginGroup(QStringLiteral("WebService"));
- QStringList hotkeys = qt_config->childGroups();
- for (auto hotkey : hotkeys) {
- qt_config->beginGroup(hotkey);
- UISettings::values.shortcuts.emplace_back(UISettings::Shortcut(
- group + "/" + hotkey,
- UISettings::ContextualShortcut(ReadSetting("KeySeq").toString(),
- ReadSetting("Context").toInt())));
- qt_config->endGroup();
- }
+ Settings::values.enable_telemetry =
+ ReadSetting(QStringLiteral("enable_telemetry"), true).toBool();
+ Settings::values.web_api_url =
+ ReadSetting(QStringLiteral("web_api_url"), QStringLiteral("https://api.yuzu-emu.org"))
+ .toString()
+ .toStdString();
+ Settings::values.yuzu_username =
+ ReadSetting(QStringLiteral("yuzu_username")).toString().toStdString();
+ Settings::values.yuzu_token =
+ ReadSetting(QStringLiteral("yuzu_token")).toString().toStdString();
- qt_config->endGroup();
- }
qt_config->endGroup();
+}
- UISettings::values.single_window_mode = ReadSetting("singleWindowMode", true).toBool();
- UISettings::values.fullscreen = ReadSetting("fullscreen", false).toBool();
- UISettings::values.display_titlebar = ReadSetting("displayTitleBars", true).toBool();
- UISettings::values.show_filter_bar = ReadSetting("showFilterBar", true).toBool();
- UISettings::values.show_status_bar = ReadSetting("showStatusBar", true).toBool();
- UISettings::values.confirm_before_closing = ReadSetting("confirmClose", true).toBool();
- UISettings::values.first_start = ReadSetting("firstStart", true).toBool();
- UISettings::values.callout_flags = ReadSetting("calloutFlags", 0).toUInt();
- UISettings::values.show_console = ReadSetting("showConsole", false).toBool();
- UISettings::values.profile_index = ReadSetting("profileIndex", 0).toUInt();
-
- ApplyDefaultProfileIfInputInvalid();
-
- qt_config->endGroup();
+void Config::ReadValues() {
+ ReadControlValues();
+ ReadCoreValues();
+ ReadRendererValues();
+ ReadAudioValues();
+ ReadDataStorageValues();
+ ReadSystemValues();
+ ReadMiscellaneousValues();
+ ReadDebugValues();
+ ReadWebServiceValues();
+ ReadDisabledAddOnValues();
+ ReadUIValues();
}
void Config::SavePlayerValues() {
for (std::size_t p = 0; p < Settings::values.players.size(); ++p) {
const auto& player = Settings::values.players[p];
- WriteSetting(QString("player_%1_connected").arg(p), player.connected, false);
- WriteSetting(QString("player_%1_type").arg(p), static_cast<u8>(player.type),
+ WriteSetting(QStringLiteral("player_%1_connected").arg(p), player.connected, false);
+ WriteSetting(QStringLiteral("player_%1_type").arg(p), static_cast<u8>(player.type),
static_cast<u8>(Settings::ControllerType::DualJoycon));
- WriteSetting(QString("player_%1_body_color_left").arg(p), player.body_color_left,
+ WriteSetting(QStringLiteral("player_%1_body_color_left").arg(p), player.body_color_left,
Settings::JOYCON_BODY_NEON_BLUE);
- WriteSetting(QString("player_%1_body_color_right").arg(p), player.body_color_right,
+ WriteSetting(QStringLiteral("player_%1_body_color_right").arg(p), player.body_color_right,
Settings::JOYCON_BODY_NEON_RED);
- WriteSetting(QString("player_%1_button_color_left").arg(p), player.button_color_left,
+ WriteSetting(QStringLiteral("player_%1_button_color_left").arg(p), player.button_color_left,
Settings::JOYCON_BUTTONS_NEON_BLUE);
- WriteSetting(QString("player_%1_button_color_right").arg(p), player.button_color_right,
- Settings::JOYCON_BUTTONS_NEON_RED);
+ WriteSetting(QStringLiteral("player_%1_button_color_right").arg(p),
+ player.button_color_right, Settings::JOYCON_BUTTONS_NEON_RED);
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
- std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
- WriteSetting(QString("player_%1_").arg(p) +
+ 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) {
- std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
+ const 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);
- WriteSetting(QString("player_%1_").arg(p) +
+ WriteSetting(QStringLiteral("player_%1_").arg(p) +
QString::fromStdString(Settings::NativeAnalog::mapping[i]),
QString::fromStdString(player.analogs[i]),
QString::fromStdString(default_param));
@@ -580,19 +735,19 @@ void Config::SavePlayerValues() {
}
void Config::SaveDebugValues() {
- WriteSetting("debug_pad_enabled", Settings::values.debug_pad_enabled, false);
+ WriteSetting(QStringLiteral("debug_pad_enabled"), Settings::values.debug_pad_enabled, false);
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
- std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
- WriteSetting(QString("debug_pad_") +
+ const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
+ WriteSetting(QStringLiteral("debug_pad_") +
QString::fromStdString(Settings::NativeButton::mapping[i]),
QString::fromStdString(Settings::values.debug_pad_buttons[i]),
QString::fromStdString(default_param));
}
for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
- std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
+ const 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);
- WriteSetting(QString("debug_pad_") +
+ WriteSetting(QStringLiteral("debug_pad_") +
QString::fromStdString(Settings::NativeAnalog::mapping[i]),
QString::fromStdString(Settings::values.debug_pad_analogs[i]),
QString::fromStdString(default_param));
@@ -600,11 +755,12 @@ void Config::SaveDebugValues() {
}
void Config::SaveMouseValues() {
- WriteSetting("mouse_enabled", Settings::values.mouse_enabled, false);
+ WriteSetting(QStringLiteral("mouse_enabled"), Settings::values.mouse_enabled, false);
for (int i = 0; i < Settings::NativeMouseButton::NumMouseButtons; ++i) {
- std::string default_param = InputCommon::GenerateKeyboardParam(default_mouse_buttons[i]);
- WriteSetting(QString("mouse_") +
+ const std::string default_param =
+ InputCommon::GenerateKeyboardParam(default_mouse_buttons[i]);
+ WriteSetting(QStringLiteral("mouse_") +
QString::fromStdString(Settings::NativeMouseButton::mapping[i]),
QString::fromStdString(Settings::values.mouse_buttons[i]),
QString::fromStdString(default_param));
@@ -612,170 +768,276 @@ void Config::SaveMouseValues() {
}
void Config::SaveTouchscreenValues() {
- WriteSetting("touchscreen_enabled", Settings::values.touchscreen.enabled, true);
- WriteSetting("touchscreen_device", QString::fromStdString(Settings::values.touchscreen.device),
- "engine:emu_window");
+ const auto& touchscreen = Settings::values.touchscreen;
- WriteSetting("touchscreen_finger", Settings::values.touchscreen.finger, 0);
- WriteSetting("touchscreen_angle", Settings::values.touchscreen.rotation_angle, 0);
- WriteSetting("touchscreen_diameter_x", Settings::values.touchscreen.diameter_x, 15);
- WriteSetting("touchscreen_diameter_y", Settings::values.touchscreen.diameter_y, 15);
+ 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::SaveValues() {
- qt_config->beginGroup("Controls");
+ SaveControlValues();
+ SaveCoreValues();
+ SaveRendererValues();
+ SaveAudioValues();
+ SaveDataStorageValues();
+ SaveSystemValues();
+ SaveMiscellaneousValues();
+ SaveDebuggingValues();
+ SaveWebServiceValues();
+ SaveDisabledAddOnValues();
+ SaveUIValues();
+}
+
+void Config::SaveAudioValues() {
+ qt_config->beginGroup(QStringLiteral("Audio"));
+
+ WriteSetting(QStringLiteral("output_engine"), QString::fromStdString(Settings::values.sink_id),
+ QStringLiteral("auto"));
+ WriteSetting(QStringLiteral("enable_audio_stretching"),
+ Settings::values.enable_audio_stretching, true);
+ WriteSetting(QStringLiteral("output_device"),
+ QString::fromStdString(Settings::values.audio_device_id), QStringLiteral("auto"));
+ WriteSetting(QStringLiteral("volume"), Settings::values.volume, 1.0f);
+
+ qt_config->endGroup();
+}
+
+void Config::SaveControlValues() {
+ qt_config->beginGroup(QStringLiteral("Controls"));
SavePlayerValues();
SaveDebugValues();
SaveMouseValues();
SaveTouchscreenValues();
- WriteSetting("motion_device", QString::fromStdString(Settings::values.motion_device),
- "engine:motion_emu,update_period:100,sensitivity:0.01");
- WriteSetting("keyboard_enabled", Settings::values.keyboard_enabled, false);
+ WriteSetting(QStringLiteral("motion_device"),
+ QString::fromStdString(Settings::values.motion_device),
+ QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01"));
+ WriteSetting(QStringLiteral("keyboard_enabled"), Settings::values.keyboard_enabled, false);
qt_config->endGroup();
+}
- qt_config->beginGroup("Core");
- WriteSetting("use_cpu_jit", Settings::values.use_cpu_jit, true);
- WriteSetting("use_multi_core", Settings::values.use_multi_core, false);
- qt_config->endGroup();
+void Config::SaveCoreValues() {
+ qt_config->beginGroup(QStringLiteral("Core"));
- qt_config->beginGroup("Renderer");
- WriteSetting("resolution_factor", (double)Settings::values.resolution_factor, 1.0);
- WriteSetting("use_frame_limit", Settings::values.use_frame_limit, true);
- WriteSetting("frame_limit", Settings::values.frame_limit, 100);
- WriteSetting("use_disk_shader_cache", Settings::values.use_disk_shader_cache, true);
- WriteSetting("use_accurate_gpu_emulation", Settings::values.use_accurate_gpu_emulation, false);
- WriteSetting("use_asynchronous_gpu_emulation", Settings::values.use_asynchronous_gpu_emulation,
- false);
+ WriteSetting(QStringLiteral("use_cpu_jit"), Settings::values.use_cpu_jit, true);
+ WriteSetting(QStringLiteral("use_multi_core"), Settings::values.use_multi_core, false);
- // Cast to double because Qt's written float values are not human-readable
- WriteSetting("bg_red", (double)Settings::values.bg_red, 0.0);
- WriteSetting("bg_green", (double)Settings::values.bg_green, 0.0);
- WriteSetting("bg_blue", (double)Settings::values.bg_blue, 0.0);
qt_config->endGroup();
+}
- qt_config->beginGroup("Audio");
- WriteSetting("output_engine", QString::fromStdString(Settings::values.sink_id), "auto");
- WriteSetting("enable_audio_stretching", Settings::values.enable_audio_stretching, true);
- WriteSetting("output_device", QString::fromStdString(Settings::values.audio_device_id), "auto");
- WriteSetting("volume", Settings::values.volume, 1.0f);
- qt_config->endGroup();
+void Config::SaveDataStorageValues() {
+ qt_config->beginGroup(QStringLiteral("Data Storage"));
- qt_config->beginGroup("Data Storage");
- WriteSetting("use_virtual_sd", Settings::values.use_virtual_sd, true);
- WriteSetting("nand_directory",
+ WriteSetting(QStringLiteral("use_virtual_sd"), Settings::values.use_virtual_sd, true);
+ WriteSetting(QStringLiteral("nand_directory"),
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)),
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)));
- WriteSetting("sdmc_directory",
+ WriteSetting(QStringLiteral("sdmc_directory"),
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)),
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)));
- qt_config->endGroup();
-
- qt_config->beginGroup("System");
- WriteSetting("use_docked_mode", Settings::values.use_docked_mode, false);
- WriteSetting("enable_nfc", Settings::values.enable_nfc, true);
- WriteSetting("current_user", Settings::values.current_user, 0);
- WriteSetting("language_index", Settings::values.language_index, 1);
-
- WriteSetting("rng_seed_enabled", Settings::values.rng_seed.has_value(), false);
- WriteSetting("rng_seed", Settings::values.rng_seed.value_or(0), 0);
-
- WriteSetting("custom_rtc_enabled", Settings::values.custom_rtc.has_value(), false);
- WriteSetting("custom_rtc",
- QVariant::fromValue<long long>(
- Settings::values.custom_rtc.value_or(std::chrono::seconds{}).count()),
- 0);
qt_config->endGroup();
+}
- qt_config->beginGroup("Miscellaneous");
- WriteSetting("log_filter", QString::fromStdString(Settings::values.log_filter), "*:Info");
- WriteSetting("use_dev_keys", Settings::values.use_dev_keys, false);
- qt_config->endGroup();
+void Config::SaveDebuggingValues() {
+ qt_config->beginGroup(QStringLiteral("Debugging"));
- qt_config->beginGroup("Debugging");
- WriteSetting("use_gdbstub", Settings::values.use_gdbstub, false);
- WriteSetting("gdbstub_port", Settings::values.gdbstub_port, 24689);
- WriteSetting("program_args", QString::fromStdString(Settings::values.program_args), "");
- WriteSetting("dump_exefs", Settings::values.dump_exefs, false);
- WriteSetting("dump_nso", Settings::values.dump_nso, false);
- qt_config->endGroup();
+ 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), QStringLiteral(""));
+ WriteSetting(QStringLiteral("dump_exefs"), Settings::values.dump_exefs, false);
+ WriteSetting(QStringLiteral("dump_nso"), Settings::values.dump_nso, false);
- qt_config->beginGroup("WebService");
- WriteSetting("enable_telemetry", Settings::values.enable_telemetry, true);
- WriteSetting("web_api_url", QString::fromStdString(Settings::values.web_api_url),
- "https://api.yuzu-emu.org");
- WriteSetting("yuzu_username", QString::fromStdString(Settings::values.yuzu_username));
- WriteSetting("yuzu_token", QString::fromStdString(Settings::values.yuzu_token));
qt_config->endGroup();
+}
+
+void Config::SaveDisabledAddOnValues() {
+ qt_config->beginWriteArray(QStringLiteral("DisabledAddOns"));
- qt_config->beginWriteArray("DisabledAddOns");
int i = 0;
for (const auto& elem : Settings::values.disabled_addons) {
qt_config->setArrayIndex(i);
- WriteSetting("title_id", QVariant::fromValue<u64>(elem.first), 0);
- qt_config->beginWriteArray("disabled");
+ WriteSetting(QStringLiteral("title_id"), QVariant::fromValue<u64>(elem.first), 0);
+ qt_config->beginWriteArray(QStringLiteral("disabled"));
for (std::size_t j = 0; j < elem.second.size(); ++j) {
qt_config->setArrayIndex(static_cast<int>(j));
- WriteSetting("d", QString::fromStdString(elem.second[j]), "");
+ WriteSetting(QStringLiteral("d"), QString::fromStdString(elem.second[j]),
+ QStringLiteral(""));
}
qt_config->endArray();
++i;
}
+
qt_config->endArray();
+}
+
+void Config::SaveMiscellaneousValues() {
+ qt_config->beginGroup(QStringLiteral("Miscellaneous"));
+
+ WriteSetting(QStringLiteral("log_filter"), QString::fromStdString(Settings::values.log_filter),
+ QStringLiteral("*:Info"));
+ WriteSetting(QStringLiteral("use_dev_keys"), Settings::values.use_dev_keys, false);
- qt_config->beginGroup("UI");
- WriteSetting("theme", UISettings::values.theme, UISettings::themes[0].second);
- WriteSetting("enable_discord_presence", UISettings::values.enable_discord_presence, true);
- WriteSetting("screenshot_resolution_factor", UISettings::values.screenshot_resolution_factor,
- 0);
- WriteSetting("select_user_on_boot", UISettings::values.select_user_on_boot, false);
-
- qt_config->beginGroup("UIGameList");
- WriteSetting("show_unknown", UISettings::values.show_unknown, true);
- WriteSetting("show_add_ons", UISettings::values.show_add_ons, true);
- WriteSetting("icon_size", UISettings::values.icon_size, 64);
- WriteSetting("row_1_text_id", UISettings::values.row_1_text_id, 3);
- WriteSetting("row_2_text_id", UISettings::values.row_2_text_id, 2);
qt_config->endGroup();
+}
+
+void Config::SavePathValues() {
+ qt_config->beginGroup(QStringLiteral("Paths"));
+
+ WriteSetting(QStringLiteral("romsPath"), UISettings::values.roms_path);
+ WriteSetting(QStringLiteral("symbolsPath"), UISettings::values.symbols_path);
+ WriteSetting(QStringLiteral("screenshotPath"), UISettings::values.screenshot_path);
+ WriteSetting(QStringLiteral("gameListRootDir"), UISettings::values.game_directory_path,
+ QStringLiteral("."));
+ WriteSetting(QStringLiteral("gameListDeepScan"), UISettings::values.game_directory_deepscan,
+ false);
+ WriteSetting(QStringLiteral("recentFiles"), UISettings::values.recent_files);
- qt_config->beginGroup("UILayout");
- WriteSetting("geometry", UISettings::values.geometry);
- WriteSetting("state", UISettings::values.state);
- WriteSetting("geometryRenderWindow", UISettings::values.renderwindow_geometry);
- WriteSetting("gameListHeaderState", UISettings::values.gamelist_header_state);
- WriteSetting("microProfileDialogGeometry", UISettings::values.microprofile_geometry);
- WriteSetting("microProfileDialogVisible", UISettings::values.microprofile_visible, false);
qt_config->endGroup();
+}
+
+void Config::SaveRendererValues() {
+ qt_config->beginGroup(QStringLiteral("Renderer"));
+
+ WriteSetting(QStringLiteral("resolution_factor"),
+ static_cast<double>(Settings::values.resolution_factor), 1.0);
+ WriteSetting(QStringLiteral("use_frame_limit"), Settings::values.use_frame_limit, true);
+ WriteSetting(QStringLiteral("frame_limit"), Settings::values.frame_limit, 100);
+ WriteSetting(QStringLiteral("use_disk_shader_cache"), Settings::values.use_disk_shader_cache,
+ true);
+ WriteSetting(QStringLiteral("use_accurate_gpu_emulation"),
+ Settings::values.use_accurate_gpu_emulation, false);
+ WriteSetting(QStringLiteral("use_asynchronous_gpu_emulation"),
+ Settings::values.use_asynchronous_gpu_emulation, false);
+ WriteSetting(QStringLiteral("force_30fps_mode"), Settings::values.force_30fps_mode, false);
+
+ // Cast to double because Qt's written float values are not human-readable
+ WriteSetting(QStringLiteral("bg_red"), static_cast<double>(Settings::values.bg_red), 0.0);
+ WriteSetting(QStringLiteral("bg_green"), static_cast<double>(Settings::values.bg_green), 0.0);
+ WriteSetting(QStringLiteral("bg_blue"), static_cast<double>(Settings::values.bg_blue), 0.0);
- qt_config->beginGroup("Paths");
- WriteSetting("romsPath", UISettings::values.roms_path);
- WriteSetting("symbolsPath", UISettings::values.symbols_path);
- WriteSetting("screenshotPath", UISettings::values.screenshot_path);
- WriteSetting("gameListRootDir", UISettings::values.gamedir, ".");
- WriteSetting("gameListDeepScan", UISettings::values.gamedir_deepscan, false);
- WriteSetting("recentFiles", UISettings::values.recent_files);
qt_config->endGroup();
+}
+
+void Config::SaveShortcutValues() {
+ qt_config->beginGroup(QStringLiteral("Shortcuts"));
- qt_config->beginGroup("Shortcuts");
- for (auto shortcut : UISettings::values.shortcuts) {
- WriteSetting(shortcut.first + "/KeySeq", shortcut.second.first);
- WriteSetting(shortcut.first + "/Context", shortcut.second.second);
+ // Lengths of UISettings::values.shortcuts & default_hotkeys are same.
+ // However, their ordering must also be the same.
+ for (std::size_t i = 0; i < default_hotkeys.size(); i++) {
+ const auto& [name, group, shortcut] = UISettings::values.shortcuts[i];
+ const auto& default_hotkey = default_hotkeys[i].shortcut;
+
+ qt_config->beginGroup(group);
+ qt_config->beginGroup(name);
+ WriteSetting(QStringLiteral("KeySeq"), shortcut.first, default_hotkey.first);
+ WriteSetting(QStringLiteral("Context"), shortcut.second, default_hotkey.second);
+ qt_config->endGroup();
+ qt_config->endGroup();
}
+
qt_config->endGroup();
+}
+
+void Config::SaveSystemValues() {
+ qt_config->beginGroup(QStringLiteral("System"));
+
+ WriteSetting(QStringLiteral("use_docked_mode"), Settings::values.use_docked_mode, false);
+ WriteSetting(QStringLiteral("current_user"), Settings::values.current_user, 0);
+ WriteSetting(QStringLiteral("language_index"), Settings::values.language_index, 1);
+
+ WriteSetting(QStringLiteral("rng_seed_enabled"), Settings::values.rng_seed.has_value(), false);
+ WriteSetting(QStringLiteral("rng_seed"), Settings::values.rng_seed.value_or(0), 0);
+
+ WriteSetting(QStringLiteral("custom_rtc_enabled"), Settings::values.custom_rtc.has_value(),
+ false);
+ WriteSetting(QStringLiteral("custom_rtc"),
+ QVariant::fromValue<long long>(
+ Settings::values.custom_rtc.value_or(std::chrono::seconds{}).count()),
+ 0);
+
+ qt_config->endGroup();
+}
+
+void Config::SaveUIValues() {
+ qt_config->beginGroup(QStringLiteral("UI"));
+
+ WriteSetting(QStringLiteral("theme"), UISettings::values.theme,
+ QString::fromUtf8(UISettings::themes[0].second));
+ WriteSetting(QStringLiteral("enable_discord_presence"),
+ UISettings::values.enable_discord_presence, true);
+ WriteSetting(QStringLiteral("screenshot_resolution_factor"),
+ UISettings::values.screenshot_resolution_factor, 0);
+ WriteSetting(QStringLiteral("select_user_on_boot"), UISettings::values.select_user_on_boot,
+ false);
+
+ SaveUIGamelistValues();
+ SaveUILayoutValues();
+ SavePathValues();
+ SaveShortcutValues();
+
+ WriteSetting(QStringLiteral("singleWindowMode"), UISettings::values.single_window_mode, true);
+ WriteSetting(QStringLiteral("fullscreen"), UISettings::values.fullscreen, false);
+ WriteSetting(QStringLiteral("displayTitleBars"), UISettings::values.display_titlebar, true);
+ WriteSetting(QStringLiteral("showFilterBar"), UISettings::values.show_filter_bar, true);
+ WriteSetting(QStringLiteral("showStatusBar"), UISettings::values.show_status_bar, true);
+ WriteSetting(QStringLiteral("confirmClose"), UISettings::values.confirm_before_closing, true);
+ WriteSetting(QStringLiteral("firstStart"), UISettings::values.first_start, true);
+ WriteSetting(QStringLiteral("calloutFlags"), UISettings::values.callout_flags, 0);
+ WriteSetting(QStringLiteral("showConsole"), UISettings::values.show_console, false);
+ WriteSetting(QStringLiteral("profileIndex"), UISettings::values.profile_index, 0);
+
+ qt_config->endGroup();
+}
+
+void Config::SaveUIGamelistValues() {
+ qt_config->beginGroup(QStringLiteral("UIGameList"));
+
+ WriteSetting(QStringLiteral("show_unknown"), UISettings::values.show_unknown, true);
+ WriteSetting(QStringLiteral("show_add_ons"), UISettings::values.show_add_ons, true);
+ WriteSetting(QStringLiteral("icon_size"), UISettings::values.icon_size, 64);
+ WriteSetting(QStringLiteral("row_1_text_id"), UISettings::values.row_1_text_id, 3);
+ WriteSetting(QStringLiteral("row_2_text_id"), UISettings::values.row_2_text_id, 2);
+ WriteSetting(QStringLiteral("cache_game_list"), UISettings::values.cache_game_list, true);
+
+ qt_config->endGroup();
+}
+
+void Config::SaveUILayoutValues() {
+ qt_config->beginGroup(QStringLiteral("UILayout"));
+
+ WriteSetting(QStringLiteral("geometry"), UISettings::values.geometry);
+ WriteSetting(QStringLiteral("state"), UISettings::values.state);
+ WriteSetting(QStringLiteral("geometryRenderWindow"), UISettings::values.renderwindow_geometry);
+ WriteSetting(QStringLiteral("gameListHeaderState"), UISettings::values.gamelist_header_state);
+ WriteSetting(QStringLiteral("microProfileDialogGeometry"),
+ UISettings::values.microprofile_geometry);
+ WriteSetting(QStringLiteral("microProfileDialogVisible"),
+ UISettings::values.microprofile_visible, false);
+
+ qt_config->endGroup();
+}
+
+void Config::SaveWebServiceValues() {
+ qt_config->beginGroup(QStringLiteral("WebService"));
+
+ WriteSetting(QStringLiteral("enable_telemetry"), Settings::values.enable_telemetry, true);
+ WriteSetting(QStringLiteral("web_api_url"),
+ QString::fromStdString(Settings::values.web_api_url),
+ QStringLiteral("https://api.yuzu-emu.org"));
+ WriteSetting(QStringLiteral("yuzu_username"),
+ QString::fromStdString(Settings::values.yuzu_username));
+ WriteSetting(QStringLiteral("yuzu_token"), QString::fromStdString(Settings::values.yuzu_token));
- WriteSetting("singleWindowMode", UISettings::values.single_window_mode, true);
- WriteSetting("fullscreen", UISettings::values.fullscreen, false);
- WriteSetting("displayTitleBars", UISettings::values.display_titlebar, true);
- WriteSetting("showFilterBar", UISettings::values.show_filter_bar, true);
- WriteSetting("showStatusBar", UISettings::values.show_status_bar, true);
- WriteSetting("confirmClose", UISettings::values.confirm_before_closing, true);
- WriteSetting("firstStart", UISettings::values.first_start, true);
- WriteSetting("calloutFlags", UISettings::values.callout_flags, 0);
- WriteSetting("showConsole", UISettings::values.show_console, false);
- WriteSetting("profileIndex", UISettings::values.profile_index, 0);
qt_config->endGroup();
}
@@ -785,7 +1047,7 @@ QVariant Config::ReadSetting(const QString& name) const {
QVariant Config::ReadSetting(const QString& name, const QVariant& default_value) const {
QVariant result;
- if (qt_config->value(name + "/default", false).toBool()) {
+ if (qt_config->value(name + QStringLiteral("/default"), false).toBool()) {
result = default_value;
} else {
result = qt_config->value(name, default_value);
@@ -799,7 +1061,7 @@ void Config::WriteSetting(const QString& name, const QVariant& value) {
void Config::WriteSetting(const QString& name, const QVariant& value,
const QVariant& default_value) {
- qt_config->setValue(name + "/default", value == default_value);
+ qt_config->setValue(name + QStringLiteral("/default"), value == default_value);
qt_config->setValue(name, value);
}
diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h
index f4185db18..6b523ecdd 100644
--- a/src/yuzu/configuration/config.h
+++ b/src/yuzu/configuration/config.h
@@ -36,12 +36,46 @@ private:
void ReadTouchscreenValues();
void ApplyDefaultProfileIfInputInvalid();
+ // Read functions bases off the respective config section names.
+ void ReadAudioValues();
+ void ReadControlValues();
+ void ReadCoreValues();
+ void ReadDataStorageValues();
+ void ReadDebuggingValues();
+ void ReadDisabledAddOnValues();
+ void ReadMiscellaneousValues();
+ void ReadPathValues();
+ void ReadRendererValues();
+ void ReadShortcutValues();
+ void ReadSystemValues();
+ void ReadUIValues();
+ void ReadUIGamelistValues();
+ void ReadUILayoutValues();
+ void ReadWebServiceValues();
+
void SaveValues();
void SavePlayerValues();
void SaveDebugValues();
void SaveMouseValues();
void SaveTouchscreenValues();
+ // Save functions based off the respective config section names.
+ void SaveAudioValues();
+ void SaveControlValues();
+ void SaveCoreValues();
+ void SaveDataStorageValues();
+ void SaveDebuggingValues();
+ void SaveDisabledAddOnValues();
+ void SaveMiscellaneousValues();
+ void SavePathValues();
+ void SaveRendererValues();
+ void SaveShortcutValues();
+ void SaveSystemValues();
+ void SaveUIValues();
+ void SaveUIGamelistValues();
+ void SaveUILayoutValues();
+ void SaveWebServiceValues();
+
QVariant ReadSetting(const QString& name) const;
QVariant ReadSetting(const QString& name, const QVariant& default_value) const;
void WriteSetting(const QString& name, const QVariant& value);
diff --git a/src/yuzu/configuration/configure.ui b/src/yuzu/configuration/configure.ui
index 3f03f0b77..267717bc9 100644
--- a/src/yuzu/configuration/configure.ui
+++ b/src/yuzu/configuration/configure.ui
@@ -7,9 +7,15 @@
<x>0</x>
<y>0</y>
<width>382</width>
- <height>241</height>
+ <height>650</height>
</rect>
</property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>650</height>
+ </size>
+ </property>
<property name="windowTitle">
<string>yuzu Configuration</string>
</property>
@@ -62,6 +68,11 @@
<string>Input</string>
</attribute>
</widget>
+ <widget class="ConfigureHotkeys" name="hotkeysTab">
+ <attribute name="title">
+ <string>Hotkeys</string>
+ </attribute>
+ </widget>
<widget class="ConfigureGraphics" name="graphicsTab">
<attribute name="title">
<string>Graphics</string>
@@ -150,6 +161,12 @@
<header>configuration/configure_input_simple.h</header>
<container>1</container>
</customwidget>
+ <customwidget>
+ <class>ConfigureHotkeys</class>
+ <extends>QWidget</extends>
+ <header>configuration/configure_hotkeys.h</header>
+ <container>1</container>
+ </customwidget>
</customwidgets>
<resources/>
<connections>
diff --git a/src/yuzu/configuration/configure_audio.cpp b/src/yuzu/configuration/configure_audio.cpp
index 5d9ccc6e8..f370c690f 100644
--- a/src/yuzu/configuration/configure_audio.cpp
+++ b/src/yuzu/configuration/configure_audio.cpp
@@ -4,6 +4,8 @@
#include <memory>
+#include <QSignalBlocker>
+
#include "audio_core/sink.h"
#include "audio_core/sink_details.h"
#include "core/core.h"
@@ -15,42 +17,39 @@ ConfigureAudio::ConfigureAudio(QWidget* parent)
: QWidget(parent), ui(std::make_unique<Ui::ConfigureAudio>()) {
ui->setupUi(this);
- ui->output_sink_combo_box->clear();
- ui->output_sink_combo_box->addItem("auto");
- for (const char* id : AudioCore::GetSinkIDs()) {
- ui->output_sink_combo_box->addItem(id);
- }
+ InitializeAudioOutputSinkComboBox();
connect(ui->volume_slider, &QSlider::valueChanged, this,
- &ConfigureAudio::setVolumeIndicatorText);
+ &ConfigureAudio::SetVolumeIndicatorText);
+ connect(ui->output_sink_combo_box, qOverload<int>(&QComboBox::currentIndexChanged), this,
+ &ConfigureAudio::UpdateAudioDevices);
- this->setConfiguration();
- connect(ui->output_sink_combo_box,
- static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
- &ConfigureAudio::updateAudioDevices);
+ SetConfiguration();
- ui->output_sink_combo_box->setEnabled(!Core::System::GetInstance().IsPoweredOn());
- ui->audio_device_combo_box->setEnabled(!Core::System::GetInstance().IsPoweredOn());
+ const bool is_powered_on = Core::System::GetInstance().IsPoweredOn();
+ ui->output_sink_combo_box->setEnabled(!is_powered_on);
+ ui->audio_device_combo_box->setEnabled(!is_powered_on);
}
ConfigureAudio::~ConfigureAudio() = default;
-void ConfigureAudio::setConfiguration() {
- setOutputSinkFromSinkID();
+void ConfigureAudio::SetConfiguration() {
+ SetOutputSinkFromSinkID();
// The device list cannot be pre-populated (nor listed) until the output sink is known.
- updateAudioDevices(ui->output_sink_combo_box->currentIndex());
+ UpdateAudioDevices(ui->output_sink_combo_box->currentIndex());
- setAudioDeviceFromDeviceID();
+ SetAudioDeviceFromDeviceID();
ui->toggle_audio_stretching->setChecked(Settings::values.enable_audio_stretching);
ui->volume_slider->setValue(Settings::values.volume * ui->volume_slider->maximum());
- setVolumeIndicatorText(ui->volume_slider->sliderPosition());
+ SetVolumeIndicatorText(ui->volume_slider->sliderPosition());
}
-void ConfigureAudio::setOutputSinkFromSinkID() {
- int new_sink_index = 0;
+void ConfigureAudio::SetOutputSinkFromSinkID() {
+ [[maybe_unused]] const QSignalBlocker blocker(ui->output_sink_combo_box);
+ int new_sink_index = 0;
const QString sink_id = QString::fromStdString(Settings::values.sink_id);
for (int index = 0; index < ui->output_sink_combo_box->count(); index++) {
if (ui->output_sink_combo_box->itemText(index) == sink_id) {
@@ -62,7 +61,7 @@ void ConfigureAudio::setOutputSinkFromSinkID() {
ui->output_sink_combo_box->setCurrentIndex(new_sink_index);
}
-void ConfigureAudio::setAudioDeviceFromDeviceID() {
+void ConfigureAudio::SetAudioDeviceFromDeviceID() {
int new_device_index = -1;
const QString device_id = QString::fromStdString(Settings::values.audio_device_id);
@@ -76,11 +75,11 @@ void ConfigureAudio::setAudioDeviceFromDeviceID() {
ui->audio_device_combo_box->setCurrentIndex(new_device_index);
}
-void ConfigureAudio::setVolumeIndicatorText(int percentage) {
+void ConfigureAudio::SetVolumeIndicatorText(int percentage) {
ui->volume_indicator->setText(tr("%1%", "Volume percentage (e.g. 50%)").arg(percentage));
}
-void ConfigureAudio::applyConfiguration() {
+void ConfigureAudio::ApplyConfiguration() {
Settings::values.sink_id =
ui->output_sink_combo_box->itemText(ui->output_sink_combo_box->currentIndex())
.toStdString();
@@ -92,9 +91,17 @@ void ConfigureAudio::applyConfiguration() {
static_cast<float>(ui->volume_slider->sliderPosition()) / ui->volume_slider->maximum();
}
-void ConfigureAudio::updateAudioDevices(int sink_index) {
+void ConfigureAudio::changeEvent(QEvent* event) {
+ if (event->type() == QEvent::LanguageChange) {
+ RetranslateUI();
+ }
+
+ QWidget::changeEvent(event);
+}
+
+void ConfigureAudio::UpdateAudioDevices(int sink_index) {
ui->audio_device_combo_box->clear();
- ui->audio_device_combo_box->addItem(AudioCore::auto_device_name);
+ ui->audio_device_combo_box->addItem(QString::fromUtf8(AudioCore::auto_device_name));
const std::string sink_id = ui->output_sink_combo_box->itemText(sink_index).toStdString();
for (const auto& device : AudioCore::GetDeviceListForSink(sink_id)) {
@@ -102,6 +109,16 @@ void ConfigureAudio::updateAudioDevices(int sink_index) {
}
}
-void ConfigureAudio::retranslateUi() {
+void ConfigureAudio::InitializeAudioOutputSinkComboBox() {
+ ui->output_sink_combo_box->clear();
+ ui->output_sink_combo_box->addItem(QString::fromUtf8(AudioCore::auto_device_name));
+
+ for (const char* id : AudioCore::GetSinkIDs()) {
+ ui->output_sink_combo_box->addItem(QString::fromUtf8(id));
+ }
+}
+
+void ConfigureAudio::RetranslateUI() {
ui->retranslateUi(this);
+ SetVolumeIndicatorText(ui->volume_slider->sliderPosition());
}
diff --git a/src/yuzu/configuration/configure_audio.h b/src/yuzu/configuration/configure_audio.h
index 8771421c0..ea83bd72d 100644
--- a/src/yuzu/configuration/configure_audio.h
+++ b/src/yuzu/configuration/configure_audio.h
@@ -18,16 +18,21 @@ public:
explicit ConfigureAudio(QWidget* parent = nullptr);
~ConfigureAudio() override;
- void applyConfiguration();
- void retranslateUi();
+ void ApplyConfiguration();
private:
- void updateAudioDevices(int sink_index);
+ void changeEvent(QEvent* event) override;
- void setConfiguration();
- void setOutputSinkFromSinkID();
- void setAudioDeviceFromDeviceID();
- void setVolumeIndicatorText(int percentage);
+ void InitializeAudioOutputSinkComboBox();
+
+ void RetranslateUI();
+
+ void UpdateAudioDevices(int sink_index);
+
+ void SetConfiguration();
+ void SetOutputSinkFromSinkID();
+ void SetAudioDeviceFromDeviceID();
+ void SetVolumeIndicatorText(int percentage);
std::unique_ptr<Ui::ConfigureAudio> ui;
};
diff --git a/src/yuzu/configuration/configure_audio.ui b/src/yuzu/configuration/configure_audio.ui
index a29a0e265..a098b9acc 100644
--- a/src/yuzu/configuration/configure_audio.ui
+++ b/src/yuzu/configuration/configure_audio.ui
@@ -20,7 +20,7 @@
<item>
<layout class="QHBoxLayout">
<item>
- <widget class="QLabel" name="label">
+ <widget class="QLabel" name="label_1">
<property name="text">
<string>Output Engine:</string>
</property>
@@ -44,7 +44,7 @@
<item>
<layout class="QHBoxLayout">
<item>
- <widget class="QLabel" name="label">
+ <widget class="QLabel" name="label_2">
<property name="text">
<string>Audio Device:</string>
</property>
@@ -61,7 +61,7 @@
<number>0</number>
</property>
<item>
- <widget class="QLabel" name="label">
+ <widget class="QLabel" name="label_3">
<property name="text">
<string>Volume:</string>
</property>
diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp
index 550cf9dca..efc2bedfd 100644
--- a/src/yuzu/configuration/configure_debug.cpp
+++ b/src/yuzu/configuration/configure_debug.cpp
@@ -16,7 +16,8 @@
ConfigureDebug::ConfigureDebug(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureDebug) {
ui->setupUi(this);
- this->setConfiguration();
+ SetConfiguration();
+
connect(ui->open_log_button, &QPushButton::pressed, []() {
QString path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LogDir));
QDesktopServices::openUrl(QUrl::fromLocalFile(path));
@@ -25,7 +26,7 @@ ConfigureDebug::ConfigureDebug(QWidget* parent) : QWidget(parent), ui(new Ui::Co
ConfigureDebug::~ConfigureDebug() = default;
-void ConfigureDebug::setConfiguration() {
+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);
@@ -37,7 +38,7 @@ void ConfigureDebug::setConfiguration() {
ui->dump_decompressed_nso->setChecked(Settings::values.dump_nso);
}
-void ConfigureDebug::applyConfiguration() {
+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();
@@ -50,3 +51,15 @@ void ConfigureDebug::applyConfiguration() {
filter.ParseFilterString(Settings::values.log_filter);
Log::SetGlobalFilter(filter);
}
+
+void ConfigureDebug::changeEvent(QEvent* event) {
+ if (event->type() == QEvent::LanguageChange) {
+ RetranslateUI();
+ }
+
+ QWidget::changeEvent(event);
+}
+
+void ConfigureDebug::RetranslateUI() {
+ ui->retranslateUi(this);
+}
diff --git a/src/yuzu/configuration/configure_debug.h b/src/yuzu/configuration/configure_debug.h
index c6420b18c..f4805a1d8 100644
--- a/src/yuzu/configuration/configure_debug.h
+++ b/src/yuzu/configuration/configure_debug.h
@@ -18,10 +18,13 @@ public:
explicit ConfigureDebug(QWidget* parent = nullptr);
~ConfigureDebug() override;
- void applyConfiguration();
+ void ApplyConfiguration();
private:
- void setConfiguration();
+ void changeEvent(QEvent* event) override;
+
+ void RetranslateUI();
+ void SetConfiguration();
std::unique_ptr<Ui::ConfigureDebug> ui;
};
diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui
index 758a92335..5ca9ce0e6 100644
--- a/src/yuzu/configuration/configure_debug.ui
+++ b/src/yuzu/configuration/configure_debug.ui
@@ -45,7 +45,7 @@
</spacer>
</item>
<item>
- <widget class="QLabel" name="label_2">
+ <widget class="QLabel" name="label_1">
<property name="text">
<string>Port:</string>
</property>
@@ -70,11 +70,11 @@
<property name="title">
<string>Logging</string>
</property>
- <layout class="QVBoxLayout" name="verticalLayout">
+ <layout class="QVBoxLayout" name="verticalLayout_4">
<item>
- <layout class="QHBoxLayout" name="horizontalLayout">
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
- <widget class="QLabel" name="label">
+ <widget class="QLabel" name="label_2">
<property name="text">
<string>Global Log Filter</string>
</property>
@@ -86,7 +86,7 @@
</layout>
</item>
<item>
- <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QCheckBox" name="toggle_console">
<property name="text">
@@ -111,11 +111,11 @@
<property name="title">
<string>Homebrew</string>
</property>
- <layout class="QVBoxLayout" name="verticalLayout">
+ <layout class="QVBoxLayout" name="verticalLayout_5">
<item>
- <layout class="QHBoxLayout" name="horizontalLayout">
+ <layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
- <widget class="QLabel" name="label">
+ <widget class="QLabel" name="label_3">
<property name="text">
<string>Arguments String</string>
</property>
@@ -134,7 +134,7 @@
<property name="title">
<string>Dump</string>
</property>
- <layout class="QVBoxLayout" name="verticalLayout_4">
+ <layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<widget class="QCheckBox" name="dump_decompressed_nso">
<property name="whatsThis">
diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp
index 777050405..e636964e3 100644
--- a/src/yuzu/configuration/configure_dialog.cpp
+++ b/src/yuzu/configuration/configure_dialog.cpp
@@ -4,51 +4,81 @@
#include <QHash>
#include <QListWidgetItem>
+#include <QSignalBlocker>
#include "core/settings.h"
#include "ui_configure.h"
#include "yuzu/configuration/config.h"
#include "yuzu/configuration/configure_dialog.h"
+#include "yuzu/configuration/configure_input_player.h"
#include "yuzu/hotkeys.h"
-ConfigureDialog::ConfigureDialog(QWidget* parent, const HotkeyRegistry& registry)
- : QDialog(parent), ui(new Ui::ConfigureDialog) {
+ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry)
+ : QDialog(parent), ui(new Ui::ConfigureDialog), registry(registry) {
ui->setupUi(this);
- ui->generalTab->PopulateHotkeyList(registry);
- this->setConfiguration();
- this->PopulateSelectionList();
+ ui->hotkeysTab->Populate(registry);
+ setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
+
+ SetConfiguration();
+ PopulateSelectionList();
+
connect(ui->selectorList, &QListWidget::itemSelectionChanged, this,
&ConfigureDialog::UpdateVisibleTabs);
adjustSize();
-
ui->selectorList->setCurrentRow(0);
}
ConfigureDialog::~ConfigureDialog() = default;
-void ConfigureDialog::setConfiguration() {}
-
-void ConfigureDialog::applyConfiguration() {
- ui->generalTab->applyConfiguration();
- ui->gameListTab->applyConfiguration();
- ui->systemTab->applyConfiguration();
- ui->profileManagerTab->applyConfiguration();
- ui->inputTab->applyConfiguration();
- ui->graphicsTab->applyConfiguration();
- ui->audioTab->applyConfiguration();
- ui->debugTab->applyConfiguration();
- ui->webTab->applyConfiguration();
+void ConfigureDialog::SetConfiguration() {}
+
+void ConfigureDialog::ApplyConfiguration() {
+ ui->generalTab->ApplyConfiguration();
+ ui->gameListTab->ApplyConfiguration();
+ ui->systemTab->ApplyConfiguration();
+ ui->profileManagerTab->ApplyConfiguration();
+ ui->inputTab->ApplyConfiguration();
+ ui->hotkeysTab->ApplyConfiguration(registry);
+ ui->graphicsTab->ApplyConfiguration();
+ ui->audioTab->ApplyConfiguration();
+ ui->debugTab->ApplyConfiguration();
+ ui->webTab->ApplyConfiguration();
Settings::Apply();
Settings::LogSettings();
}
+void ConfigureDialog::changeEvent(QEvent* event) {
+ if (event->type() == QEvent::LanguageChange) {
+ RetranslateUI();
+ }
+
+ QDialog::changeEvent(event);
+}
+
+void ConfigureDialog::RetranslateUI() {
+ const int old_row = ui->selectorList->currentRow();
+ const int old_index = ui->tabWidget->currentIndex();
+
+ ui->retranslateUi(this);
+
+ PopulateSelectionList();
+ ui->selectorList->setCurrentRow(old_row);
+
+ UpdateVisibleTabs();
+ ui->tabWidget->setCurrentIndex(old_index);
+}
+
void ConfigureDialog::PopulateSelectionList() {
const std::array<std::pair<QString, QStringList>, 4> items{
{{tr("General"), {tr("General"), tr("Web"), tr("Debug"), tr("Game List")}},
{tr("System"), {tr("System"), tr("Profiles"), tr("Audio")}},
{tr("Graphics"), {tr("Graphics")}},
- {tr("Controls"), {tr("Input")}}}};
+ {tr("Controls"), {tr("Input"), tr("Hotkeys")}}},
+ };
+ [[maybe_unused]] const QSignalBlocker blocker(ui->selectorList);
+
+ ui->selectorList->clear();
for (const auto& entry : items) {
auto* const item = new QListWidgetItem(entry.first);
item->setData(Qt::UserRole, entry.second);
@@ -59,23 +89,28 @@ void ConfigureDialog::PopulateSelectionList() {
void ConfigureDialog::UpdateVisibleTabs() {
const auto items = ui->selectorList->selectedItems();
- if (items.isEmpty())
+ if (items.isEmpty()) {
return;
+ }
- const std::map<QString, QWidget*> widgets = {{tr("General"), ui->generalTab},
- {tr("System"), ui->systemTab},
- {tr("Profiles"), ui->profileManagerTab},
- {tr("Input"), ui->inputTab},
- {tr("Graphics"), ui->graphicsTab},
- {tr("Audio"), ui->audioTab},
- {tr("Debug"), ui->debugTab},
- {tr("Web"), ui->webTab},
- {tr("Game List"), ui->gameListTab}};
+ const std::map<QString, QWidget*> widgets = {
+ {tr("General"), ui->generalTab},
+ {tr("System"), ui->systemTab},
+ {tr("Profiles"), ui->profileManagerTab},
+ {tr("Input"), ui->inputTab},
+ {tr("Hotkeys"), ui->hotkeysTab},
+ {tr("Graphics"), ui->graphicsTab},
+ {tr("Audio"), ui->audioTab},
+ {tr("Debug"), ui->debugTab},
+ {tr("Web"), ui->webTab},
+ {tr("Game List"), ui->gameListTab},
+ };
- ui->tabWidget->clear();
+ [[maybe_unused]] const QSignalBlocker blocker(ui->tabWidget);
+ ui->tabWidget->clear();
const QStringList tabs = items[0]->data(Qt::UserRole).toStringList();
-
- for (const auto& tab : tabs)
+ for (const auto& tab : tabs) {
ui->tabWidget->addTab(widgets.find(tab)->second, tab);
+ }
}
diff --git a/src/yuzu/configuration/configure_dialog.h b/src/yuzu/configuration/configure_dialog.h
index 243d9fa09..2d3bfc2da 100644
--- a/src/yuzu/configuration/configure_dialog.h
+++ b/src/yuzu/configuration/configure_dialog.h
@@ -17,15 +17,20 @@ class ConfigureDialog : public QDialog {
Q_OBJECT
public:
- explicit ConfigureDialog(QWidget* parent, const HotkeyRegistry& registry);
+ explicit ConfigureDialog(QWidget* parent, HotkeyRegistry& registry);
~ConfigureDialog() override;
- void applyConfiguration();
+ void ApplyConfiguration();
private:
- void setConfiguration();
+ void changeEvent(QEvent* event) override;
+
+ void RetranslateUI();
+
+ void SetConfiguration();
void UpdateVisibleTabs();
void PopulateSelectionList();
std::unique_ptr<Ui::ConfigureDialog> ui;
+ HotkeyRegistry& registry;
};
diff --git a/src/yuzu/configuration/configure_gamelist.cpp b/src/yuzu/configuration/configure_gamelist.cpp
index ae8cac243..d1724ba89 100644
--- a/src/yuzu/configuration/configure_gamelist.cpp
+++ b/src/yuzu/configuration/configure_gamelist.cpp
@@ -12,20 +12,20 @@
#include "yuzu/ui_settings.h"
namespace {
-constexpr std::array<std::pair<u32, const char*>, 5> default_icon_sizes{{
+constexpr std::array default_icon_sizes{
std::make_pair(0, QT_TR_NOOP("None")),
std::make_pair(32, QT_TR_NOOP("Small (32x32)")),
std::make_pair(64, QT_TR_NOOP("Standard (64x64)")),
std::make_pair(128, QT_TR_NOOP("Large (128x128)")),
std::make_pair(256, QT_TR_NOOP("Full Size (256x256)")),
-}};
+};
-constexpr std::array<const char*, 4> row_text_names{{
+constexpr std::array row_text_names{
QT_TR_NOOP("Filename"),
QT_TR_NOOP("Filetype"),
QT_TR_NOOP("Title ID"),
QT_TR_NOOP("Title Name"),
-}};
+};
} // Anonymous namespace
ConfigureGameList::ConfigureGameList(QWidget* parent)
@@ -35,7 +35,7 @@ ConfigureGameList::ConfigureGameList(QWidget* parent)
InitializeIconSizeComboBox();
InitializeRowComboBoxes();
- this->setConfiguration();
+ SetConfiguration();
// Force game list reload if any of the relevant settings are changed.
connect(ui->show_unknown, &QCheckBox::stateChanged, this,
@@ -50,7 +50,7 @@ ConfigureGameList::ConfigureGameList(QWidget* parent)
ConfigureGameList::~ConfigureGameList() = default;
-void ConfigureGameList::applyConfiguration() {
+void ConfigureGameList::ApplyConfiguration() {
UISettings::values.show_unknown = ui->show_unknown->isChecked();
UISettings::values.show_add_ons = ui->show_add_ons->isChecked();
UISettings::values.icon_size = ui->icon_size_combobox->currentData().toUInt();
@@ -63,7 +63,7 @@ void ConfigureGameList::RequestGameListUpdate() {
UISettings::values.is_game_list_reload_pending.exchange(true);
}
-void ConfigureGameList::setConfiguration() {
+void ConfigureGameList::SetConfiguration() {
ui->show_unknown->setChecked(UISettings::values.show_unknown);
ui->show_add_ons->setChecked(UISettings::values.show_add_ons);
ui->icon_size_combobox->setCurrentIndex(
@@ -77,7 +77,6 @@ void ConfigureGameList::setConfiguration() {
void ConfigureGameList::changeEvent(QEvent* event) {
if (event->type() == QEvent::LanguageChange) {
RetranslateUI();
- return;
}
QWidget::changeEvent(event);
@@ -100,13 +99,15 @@ void ConfigureGameList::RetranslateUI() {
void ConfigureGameList::InitializeIconSizeComboBox() {
for (const auto& size : default_icon_sizes) {
- ui->icon_size_combobox->addItem(size.second, size.first);
+ ui->icon_size_combobox->addItem(QString::fromUtf8(size.second), size.first);
}
}
void ConfigureGameList::InitializeRowComboBoxes() {
for (std::size_t i = 0; i < row_text_names.size(); ++i) {
- ui->row_1_text_combobox->addItem(row_text_names[i], QVariant::fromValue(i));
- ui->row_2_text_combobox->addItem(row_text_names[i], QVariant::fromValue(i));
+ const QString row_text_name = QString::fromUtf8(row_text_names[i]);
+
+ ui->row_1_text_combobox->addItem(row_text_name, QVariant::fromValue(i));
+ ui->row_2_text_combobox->addItem(row_text_name, QVariant::fromValue(i));
}
}
diff --git a/src/yuzu/configuration/configure_gamelist.h b/src/yuzu/configuration/configure_gamelist.h
index bf3f1cdfa..e11822919 100644
--- a/src/yuzu/configuration/configure_gamelist.h
+++ b/src/yuzu/configuration/configure_gamelist.h
@@ -18,12 +18,12 @@ public:
explicit ConfigureGameList(QWidget* parent = nullptr);
~ConfigureGameList() override;
- void applyConfiguration();
+ void ApplyConfiguration();
private:
void RequestGameListUpdate();
- void setConfiguration();
+ void SetConfiguration();
void changeEvent(QEvent*) override;
void RetranslateUI();
diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp
index 4116b6cd7..06d368dfc 100644
--- a/src/yuzu/configuration/configure_general.cpp
+++ b/src/yuzu/configuration/configure_general.cpp
@@ -14,10 +14,11 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent)
ui->setupUi(this);
for (const auto& theme : UISettings::themes) {
- ui->theme_combobox->addItem(theme.first, theme.second);
+ ui->theme_combobox->addItem(QString::fromUtf8(theme.first),
+ QString::fromUtf8(theme.second));
}
- this->setConfiguration();
+ SetConfiguration();
connect(ui->toggle_deepscan, &QCheckBox::stateChanged, this,
[] { UISettings::values.is_game_list_reload_pending.exchange(true); });
@@ -27,26 +28,32 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent)
ConfigureGeneral::~ConfigureGeneral() = default;
-void ConfigureGeneral::setConfiguration() {
- ui->toggle_deepscan->setChecked(UISettings::values.gamedir_deepscan);
+void ConfigureGeneral::SetConfiguration() {
+ ui->toggle_deepscan->setChecked(UISettings::values.game_directory_deepscan);
ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing);
ui->toggle_user_on_boot->setChecked(UISettings::values.select_user_on_boot);
ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme));
ui->use_cpu_jit->setChecked(Settings::values.use_cpu_jit);
- ui->enable_nfc->setChecked(Settings::values.enable_nfc);
}
-void ConfigureGeneral::PopulateHotkeyList(const HotkeyRegistry& registry) {
- ui->widget->Populate(registry);
-}
-
-void ConfigureGeneral::applyConfiguration() {
- UISettings::values.gamedir_deepscan = ui->toggle_deepscan->isChecked();
+void ConfigureGeneral::ApplyConfiguration() {
+ UISettings::values.game_directory_deepscan = ui->toggle_deepscan->isChecked();
UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked();
UISettings::values.select_user_on_boot = ui->toggle_user_on_boot->isChecked();
UISettings::values.theme =
ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString();
Settings::values.use_cpu_jit = ui->use_cpu_jit->isChecked();
- Settings::values.enable_nfc = ui->enable_nfc->isChecked();
+}
+
+void ConfigureGeneral::changeEvent(QEvent* event) {
+ if (event->type() == QEvent::LanguageChange) {
+ RetranslateUI();
+ }
+
+ QWidget::changeEvent(event);
+}
+
+void ConfigureGeneral::RetranslateUI() {
+ ui->retranslateUi(this);
}
diff --git a/src/yuzu/configuration/configure_general.h b/src/yuzu/configuration/configure_general.h
index 59738af40..ef05ce065 100644
--- a/src/yuzu/configuration/configure_general.h
+++ b/src/yuzu/configuration/configure_general.h
@@ -20,11 +20,13 @@ public:
explicit ConfigureGeneral(QWidget* parent = nullptr);
~ConfigureGeneral() override;
- void PopulateHotkeyList(const HotkeyRegistry& registry);
- void applyConfiguration();
+ void ApplyConfiguration();
private:
- void setConfiguration();
+ void changeEvent(QEvent* event) override;
+ void RetranslateUI();
+
+ void SetConfiguration();
std::unique_ptr<Ui::ConfigureGeneral> ui;
};
diff --git a/src/yuzu/configuration/configure_general.ui b/src/yuzu/configuration/configure_general.ui
index dff0ad5d0..1a5721fe7 100644
--- a/src/yuzu/configuration/configure_general.ui
+++ b/src/yuzu/configuration/configure_general.ui
@@ -71,26 +71,6 @@
</widget>
</item>
<item>
- <widget class="QGroupBox" name="EmulationGroupBox">
- <property name="title">
- <string>Emulation</string>
- </property>
- <layout class="QHBoxLayout" name="EmulationHorizontalLayout">
- <item>
- <layout class="QVBoxLayout" name="EmulationVerticalLayout">
- <item>
- <widget class="QCheckBox" name="enable_nfc">
- <property name="text">
- <string>Enable NFC</string>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- </layout>
- </widget>
- </item>
- <item>
<widget class="QGroupBox" name="theme_group_box">
<property name="title">
<string>Theme</string>
@@ -118,22 +98,6 @@
</widget>
</item>
<item>
- <widget class="QGroupBox" name="HotKeysGroupBox">
- <property name="title">
- <string>Hotkeys</string>
- </property>
- <layout class="QHBoxLayout" name="HotKeysHorizontalLayout">
- <item>
- <layout class="QVBoxLayout" name="HotKeysVerticalLayout">
- <item>
- <widget class="GHotkeysDialog" name="widget" native="true"/>
- </item>
- </layout>
- </item>
- </layout>
- </widget>
- </item>
- <item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
@@ -150,14 +114,6 @@
</item>
</layout>
</widget>
- <customwidgets>
- <customwidget>
- <class>GHotkeysDialog</class>
- <extends>QWidget</extends>
- <header>hotkeys.h</header>
- <container>1</container>
- </customwidget>
- </customwidgets>
<resources/>
<connections/>
</ui>
diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp
index dd1d67488..2b17b250c 100644
--- a/src/yuzu/configuration/configure_graphics.cpp
+++ b/src/yuzu/configuration/configure_graphics.cpp
@@ -51,37 +51,42 @@ Resolution FromResolutionFactor(float factor) {
ConfigureGraphics::ConfigureGraphics(QWidget* parent)
: QWidget(parent), ui(new Ui::ConfigureGraphics) {
-
ui->setupUi(this);
- this->setConfiguration();
- ui->frame_limit->setEnabled(Settings::values.use_frame_limit);
- connect(ui->toggle_frame_limit, &QCheckBox::stateChanged, ui->frame_limit,
- &QSpinBox::setEnabled);
+ SetConfiguration();
+
+ connect(ui->toggle_frame_limit, &QCheckBox::toggled, ui->frame_limit, &QSpinBox::setEnabled);
connect(ui->bg_button, &QPushButton::clicked, this, [this] {
const QColor new_bg_color = QColorDialog::getColor(bg_color);
- if (!new_bg_color.isValid())
+ if (!new_bg_color.isValid()) {
return;
+ }
UpdateBackgroundColorButton(new_bg_color);
});
}
ConfigureGraphics::~ConfigureGraphics() = default;
-void ConfigureGraphics::setConfiguration() {
+void ConfigureGraphics::SetConfiguration() {
+ const bool runtime_lock = !Core::System::GetInstance().IsPoweredOn();
+
ui->resolution_factor_combobox->setCurrentIndex(
static_cast<int>(FromResolutionFactor(Settings::values.resolution_factor)));
ui->toggle_frame_limit->setChecked(Settings::values.use_frame_limit);
+ ui->frame_limit->setEnabled(ui->toggle_frame_limit->isChecked());
ui->frame_limit->setValue(Settings::values.frame_limit);
+ ui->use_disk_shader_cache->setEnabled(runtime_lock);
ui->use_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache);
ui->use_accurate_gpu_emulation->setChecked(Settings::values.use_accurate_gpu_emulation);
- ui->use_asynchronous_gpu_emulation->setEnabled(!Core::System::GetInstance().IsPoweredOn());
+ ui->use_asynchronous_gpu_emulation->setEnabled(runtime_lock);
ui->use_asynchronous_gpu_emulation->setChecked(Settings::values.use_asynchronous_gpu_emulation);
+ ui->force_30fps_mode->setEnabled(runtime_lock);
+ ui->force_30fps_mode->setChecked(Settings::values.force_30fps_mode);
UpdateBackgroundColorButton(QColor::fromRgbF(Settings::values.bg_red, Settings::values.bg_green,
Settings::values.bg_blue));
}
-void ConfigureGraphics::applyConfiguration() {
+void ConfigureGraphics::ApplyConfiguration() {
Settings::values.resolution_factor =
ToResolutionFactor(static_cast<Resolution>(ui->resolution_factor_combobox->currentIndex()));
Settings::values.use_frame_limit = ui->toggle_frame_limit->isChecked();
@@ -90,11 +95,24 @@ void ConfigureGraphics::applyConfiguration() {
Settings::values.use_accurate_gpu_emulation = ui->use_accurate_gpu_emulation->isChecked();
Settings::values.use_asynchronous_gpu_emulation =
ui->use_asynchronous_gpu_emulation->isChecked();
+ Settings::values.force_30fps_mode = ui->force_30fps_mode->isChecked();
Settings::values.bg_red = static_cast<float>(bg_color.redF());
Settings::values.bg_green = static_cast<float>(bg_color.greenF());
Settings::values.bg_blue = static_cast<float>(bg_color.blueF());
}
+void ConfigureGraphics::changeEvent(QEvent* event) {
+ if (event->type() == QEvent::LanguageChange) {
+ RetranslateUI();
+ }
+
+ QWidget::changeEvent(event);
+}
+
+void ConfigureGraphics::RetranslateUI() {
+ ui->retranslateUi(this);
+}
+
void ConfigureGraphics::UpdateBackgroundColorButton(QColor color) {
bg_color = color;
diff --git a/src/yuzu/configuration/configure_graphics.h b/src/yuzu/configuration/configure_graphics.h
index f2799822d..fae28d98e 100644
--- a/src/yuzu/configuration/configure_graphics.h
+++ b/src/yuzu/configuration/configure_graphics.h
@@ -18,10 +18,13 @@ public:
explicit ConfigureGraphics(QWidget* parent = nullptr);
~ConfigureGraphics() override;
- void applyConfiguration();
+ void ApplyConfiguration();
private:
- void setConfiguration();
+ void changeEvent(QEvent* event) override;
+ void RetranslateUI();
+
+ void SetConfiguration();
void UpdateBackgroundColorButton(QColor color);
diff --git a/src/yuzu/configuration/configure_graphics.ui b/src/yuzu/configuration/configure_graphics.ui
index c6767e0ca..15ab18ecd 100644
--- a/src/yuzu/configuration/configure_graphics.ui
+++ b/src/yuzu/configuration/configure_graphics.ui
@@ -71,6 +71,13 @@
</widget>
</item>
<item>
+ <widget class="QCheckBox" name="force_30fps_mode">
+ <property name="text">
+ <string>Force 30 FPS mode</string>
+ </property>
+ </widget>
+ </item>
+ <item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
diff --git a/src/yuzu/configuration/configure_hotkeys.cpp b/src/yuzu/configuration/configure_hotkeys.cpp
new file mode 100644
index 000000000..3ea0b8d67
--- /dev/null
+++ b/src/yuzu/configuration/configure_hotkeys.cpp
@@ -0,0 +1,129 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <QMessageBox>
+#include <QStandardItemModel>
+#include "core/settings.h"
+#include "ui_configure_hotkeys.h"
+#include "yuzu/configuration/configure_hotkeys.h"
+#include "yuzu/hotkeys.h"
+#include "yuzu/util/sequence_dialog/sequence_dialog.h"
+
+ConfigureHotkeys::ConfigureHotkeys(QWidget* parent)
+ : QWidget(parent), ui(std::make_unique<Ui::ConfigureHotkeys>()) {
+ ui->setupUi(this);
+ setFocusPolicy(Qt::ClickFocus);
+
+ model = new QStandardItemModel(this);
+ model->setColumnCount(3);
+
+ connect(ui->hotkey_list, &QTreeView::doubleClicked, this, &ConfigureHotkeys::Configure);
+ ui->hotkey_list->setModel(model);
+
+ // TODO(Kloen): Make context configurable as well (hiding the column for now)
+ ui->hotkey_list->hideColumn(2);
+
+ ui->hotkey_list->setColumnWidth(0, 200);
+ ui->hotkey_list->resizeColumnToContents(1);
+
+ RetranslateUI();
+}
+
+ConfigureHotkeys::~ConfigureHotkeys() = default;
+
+void ConfigureHotkeys::Populate(const HotkeyRegistry& registry) {
+ for (const auto& group : registry.hotkey_groups) {
+ auto* parent_item = new QStandardItem(group.first);
+ parent_item->setEditable(false);
+ for (const auto& hotkey : group.second) {
+ auto* action = new QStandardItem(hotkey.first);
+ auto* keyseq =
+ new QStandardItem(hotkey.second.keyseq.toString(QKeySequence::NativeText));
+ action->setEditable(false);
+ keyseq->setEditable(false);
+ parent_item->appendRow({action, keyseq});
+ }
+ model->appendRow(parent_item);
+ }
+
+ ui->hotkey_list->expandAll();
+}
+
+void ConfigureHotkeys::changeEvent(QEvent* event) {
+ if (event->type() == QEvent::LanguageChange) {
+ RetranslateUI();
+ }
+
+ QWidget::changeEvent(event);
+}
+
+void ConfigureHotkeys::RetranslateUI() {
+ ui->retranslateUi(this);
+
+ model->setHorizontalHeaderLabels({tr("Action"), tr("Hotkey"), tr("Context")});
+}
+
+void ConfigureHotkeys::Configure(QModelIndex index) {
+ if (!index.parent().isValid()) {
+ return;
+ }
+
+ index = index.sibling(index.row(), 1);
+ auto* const model = ui->hotkey_list->model();
+ const auto previous_key = model->data(index);
+
+ SequenceDialog hotkey_dialog{this};
+
+ const int return_code = hotkey_dialog.exec();
+ const auto key_sequence = hotkey_dialog.GetSequence();
+ if (return_code == QDialog::Rejected || key_sequence.isEmpty()) {
+ return;
+ }
+
+ if (IsUsedKey(key_sequence) && key_sequence != QKeySequence(previous_key.toString())) {
+ QMessageBox::warning(this, tr("Conflicting Key Sequence"),
+ tr("The entered key sequence is already assigned to another hotkey."));
+ } else {
+ model->setData(index, key_sequence.toString(QKeySequence::NativeText));
+ }
+}
+
+bool ConfigureHotkeys::IsUsedKey(QKeySequence key_sequence) const {
+ for (int r = 0; r < model->rowCount(); r++) {
+ const QStandardItem* const parent = model->item(r, 0);
+
+ for (int r2 = 0; r2 < parent->rowCount(); r2++) {
+ const QStandardItem* const key_seq_item = parent->child(r2, 1);
+ const auto key_seq_str = key_seq_item->text();
+ const auto key_seq = QKeySequence::fromString(key_seq_str, QKeySequence::NativeText);
+
+ if (key_sequence == key_seq) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+void ConfigureHotkeys::ApplyConfiguration(HotkeyRegistry& registry) {
+ for (int key_id = 0; key_id < model->rowCount(); key_id++) {
+ const QStandardItem* parent = model->item(key_id, 0);
+ for (int key_column_id = 0; key_column_id < parent->rowCount(); key_column_id++) {
+ const QStandardItem* action = parent->child(key_column_id, 0);
+ const QStandardItem* keyseq = parent->child(key_column_id, 1);
+ for (auto& [group, sub_actions] : registry.hotkey_groups) {
+ if (group != parent->text())
+ continue;
+ for (auto& [action_name, hotkey] : sub_actions) {
+ if (action_name != action->text())
+ continue;
+ hotkey.keyseq = QKeySequence(keyseq->text());
+ }
+ }
+ }
+ }
+
+ registry.SaveHotkeys();
+}
diff --git a/src/yuzu/configuration/configure_hotkeys.h b/src/yuzu/configuration/configure_hotkeys.h
new file mode 100644
index 000000000..8f8c6173b
--- /dev/null
+++ b/src/yuzu/configuration/configure_hotkeys.h
@@ -0,0 +1,43 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <QWidget>
+
+namespace Ui {
+class ConfigureHotkeys;
+}
+
+class HotkeyRegistry;
+class QStandardItemModel;
+
+class ConfigureHotkeys : public QWidget {
+ Q_OBJECT
+
+public:
+ explicit ConfigureHotkeys(QWidget* parent = nullptr);
+ ~ConfigureHotkeys() override;
+
+ void ApplyConfiguration(HotkeyRegistry& registry);
+
+ /**
+ * Populates the hotkey list widget using data from the provided registry.
+ * Called everytime the Configure dialog is opened.
+ * @param registry The HotkeyRegistry whose data is used to populate the list.
+ */
+ void Populate(const HotkeyRegistry& registry);
+
+private:
+ void changeEvent(QEvent* event) override;
+ void RetranslateUI();
+
+ void Configure(QModelIndex index);
+ bool IsUsedKey(QKeySequence key_sequence) const;
+
+ std::unique_ptr<Ui::ConfigureHotkeys> ui;
+
+ QStandardItemModel* model;
+};
diff --git a/src/yuzu/configuration/configure_hotkeys.ui b/src/yuzu/configuration/configure_hotkeys.ui
new file mode 100644
index 000000000..0d0b70f38
--- /dev/null
+++ b/src/yuzu/configuration/configure_hotkeys.ui
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ConfigureHotkeys</class>
+ <widget class="QWidget" name="ConfigureHotkeys">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>363</width>
+ <height>388</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Hotkey Settings</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Double-click on a binding to change it.</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTreeView" name="hotkey_list">
+ <property name="editTriggers">
+ <set>QAbstractItemView::NoEditTriggers</set>
+ </property>
+ <property name="sortingEnabled">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui> \ No newline at end of file
diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp
index f39d57998..4dd775aab 100644
--- a/src/yuzu/configuration/configure_input.cpp
+++ b/src/yuzu/configuration/configure_input.cpp
@@ -5,6 +5,7 @@
#include <algorithm>
#include <memory>
+#include <QSignalBlocker>
#include <QTimer>
#include "configuration/configure_touchscreen_advanced.h"
@@ -50,12 +51,12 @@ void OnDockedModeChanged(bool last_state, bool new_state) {
namespace {
template <typename Dialog, typename... Args>
void CallConfigureDialog(ConfigureInput& parent, Args&&... args) {
- parent.applyConfiguration();
+ parent.ApplyConfiguration();
Dialog dialog(&parent, std::forward<Args>(args)...);
const auto res = dialog.exec();
if (res == QDialog::Accepted) {
- dialog.applyConfiguration();
+ dialog.ApplyConfiguration();
}
}
} // Anonymous namespace
@@ -74,28 +75,25 @@ ConfigureInput::ConfigureInput(QWidget* parent)
ui->player5_configure, ui->player6_configure, ui->player7_configure, ui->player8_configure,
};
- for (auto* controller_box : players_controller) {
- controller_box->addItems({"None", "Pro Controller", "Dual Joycons", "Single Right Joycon",
- "Single Left Joycon"});
- }
-
- this->loadConfiguration();
- updateUIEnabled();
+ RetranslateUI();
+ LoadConfiguration();
+ UpdateUIEnabled();
connect(ui->restore_defaults_button, &QPushButton::pressed, this,
- &ConfigureInput::restoreDefaults);
+ &ConfigureInput::RestoreDefaults);
- for (auto* enabled : players_controller)
+ for (auto* enabled : players_controller) {
connect(enabled, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
- &ConfigureInput::updateUIEnabled);
- connect(ui->use_docked_mode, &QCheckBox::stateChanged, this, &ConfigureInput::updateUIEnabled);
+ &ConfigureInput::UpdateUIEnabled);
+ }
+ connect(ui->use_docked_mode, &QCheckBox::stateChanged, this, &ConfigureInput::UpdateUIEnabled);
connect(ui->handheld_connected, &QCheckBox::stateChanged, this,
- &ConfigureInput::updateUIEnabled);
- connect(ui->mouse_enabled, &QCheckBox::stateChanged, this, &ConfigureInput::updateUIEnabled);
- connect(ui->keyboard_enabled, &QCheckBox::stateChanged, this, &ConfigureInput::updateUIEnabled);
- connect(ui->debug_enabled, &QCheckBox::stateChanged, this, &ConfigureInput::updateUIEnabled);
+ &ConfigureInput::UpdateUIEnabled);
+ connect(ui->mouse_enabled, &QCheckBox::stateChanged, this, &ConfigureInput::UpdateUIEnabled);
+ connect(ui->keyboard_enabled, &QCheckBox::stateChanged, this, &ConfigureInput::UpdateUIEnabled);
+ connect(ui->debug_enabled, &QCheckBox::stateChanged, this, &ConfigureInput::UpdateUIEnabled);
connect(ui->touchscreen_enabled, &QCheckBox::stateChanged, this,
- &ConfigureInput::updateUIEnabled);
+ &ConfigureInput::UpdateUIEnabled);
for (std::size_t i = 0; i < players_configure.size(); ++i) {
connect(players_configure[i], &QPushButton::pressed, this,
@@ -117,7 +115,7 @@ ConfigureInput::ConfigureInput(QWidget* parent)
ConfigureInput::~ConfigureInput() = default;
-void ConfigureInput::applyConfiguration() {
+void ConfigureInput::ApplyConfiguration() {
for (std::size_t i = 0; i < players_controller.size(); ++i) {
const auto controller_type_index = players_controller[i]->currentIndex();
@@ -143,14 +141,41 @@ void ConfigureInput::applyConfiguration() {
Settings::values.touchscreen.enabled = ui->touchscreen_enabled->isChecked();
}
-void ConfigureInput::updateUIEnabled() {
+void ConfigureInput::changeEvent(QEvent* event) {
+ if (event->type() == QEvent::LanguageChange) {
+ RetranslateUI();
+ }
+
+ QDialog::changeEvent(event);
+}
+
+void ConfigureInput::RetranslateUI() {
+ ui->retranslateUi(this);
+ RetranslateControllerComboBoxes();
+}
+
+void ConfigureInput::RetranslateControllerComboBoxes() {
+ for (auto* controller_box : players_controller) {
+ [[maybe_unused]] const QSignalBlocker blocker(controller_box);
+
+ controller_box->clear();
+ controller_box->addItems({tr("None"), tr("Pro Controller"), tr("Dual Joycons"),
+ tr("Single Right Joycon"), tr("Single Left Joycon")});
+ }
+
+ LoadPlayerControllerIndices();
+}
+
+void ConfigureInput::UpdateUIEnabled() {
bool hit_disabled = false;
for (auto* player : players_controller) {
player->setDisabled(hit_disabled);
- if (hit_disabled)
+ if (hit_disabled) {
player->setCurrentIndex(0);
- if (!hit_disabled && player->currentIndex() == 0)
+ }
+ if (!hit_disabled && player->currentIndex() == 0) {
hit_disabled = true;
+ }
}
for (std::size_t i = 0; i < players_controller.size(); ++i) {
@@ -165,18 +190,14 @@ void ConfigureInput::updateUIEnabled() {
ui->touchscreen_advanced->setEnabled(ui->touchscreen_enabled->isChecked());
}
-void ConfigureInput::loadConfiguration() {
+void ConfigureInput::LoadConfiguration() {
std::stable_partition(
Settings::values.players.begin(),
Settings::values.players.begin() +
Service::HID::Controller_NPad::NPadIdToIndex(Service::HID::NPAD_HANDHELD),
[](const auto& player) { return player.connected; });
- for (std::size_t i = 0; i < players_controller.size(); ++i) {
- const auto connected = Settings::values.players[i].connected;
- players_controller[i]->setCurrentIndex(
- connected ? static_cast<u8>(Settings::values.players[i].type) + 1 : 0);
- }
+ LoadPlayerControllerIndices();
ui->use_docked_mode->setChecked(Settings::values.use_docked_mode);
ui->handheld_connected->setChecked(
@@ -188,10 +209,18 @@ void ConfigureInput::loadConfiguration() {
ui->keyboard_enabled->setChecked(Settings::values.keyboard_enabled);
ui->touchscreen_enabled->setChecked(Settings::values.touchscreen.enabled);
- updateUIEnabled();
+ UpdateUIEnabled();
+}
+
+void ConfigureInput::LoadPlayerControllerIndices() {
+ for (std::size_t i = 0; i < players_controller.size(); ++i) {
+ const auto connected = Settings::values.players[i].connected;
+ players_controller[i]->setCurrentIndex(
+ connected ? static_cast<u8>(Settings::values.players[i].type) + 1 : 0);
+ }
}
-void ConfigureInput::restoreDefaults() {
+void ConfigureInput::RestoreDefaults() {
players_controller[0]->setCurrentIndex(2);
for (std::size_t i = 1; i < players_controller.size(); ++i) {
@@ -204,5 +233,5 @@ void ConfigureInput::restoreDefaults() {
ui->keyboard_enabled->setCheckState(Qt::Unchecked);
ui->debug_enabled->setCheckState(Qt::Unchecked);
ui->touchscreen_enabled->setCheckState(Qt::Checked);
- updateUIEnabled();
+ UpdateUIEnabled();
}
diff --git a/src/yuzu/configuration/configure_input.h b/src/yuzu/configuration/configure_input.h
index b8e62cc2b..2f70cb3ca 100644
--- a/src/yuzu/configuration/configure_input.h
+++ b/src/yuzu/configuration/configure_input.h
@@ -30,15 +30,21 @@ public:
~ConfigureInput() override;
/// Save all button configurations to settings file
- void applyConfiguration();
+ void ApplyConfiguration();
private:
- void updateUIEnabled();
+ void changeEvent(QEvent* event) override;
+ void RetranslateUI();
+ void RetranslateControllerComboBoxes();
+
+ void UpdateUIEnabled();
/// Load configuration settings.
- void loadConfiguration();
+ void LoadConfiguration();
+ void LoadPlayerControllerIndices();
+
/// Restore all buttons to their default values.
- void restoreDefaults();
+ void RestoreDefaults();
std::unique_ptr<Ui::ConfigureInput> ui;
diff --git a/src/yuzu/configuration/configure_input.ui b/src/yuzu/configuration/configure_input.ui
index f924e3494..efffd8487 100644
--- a/src/yuzu/configuration/configure_input.ui
+++ b/src/yuzu/configuration/configure_input.ui
@@ -17,7 +17,7 @@
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
- <widget class="QGroupBox" name="gridGroupBox">
+ <widget class="QGroupBox" name="gridGroupBox_1">
<property name="title">
<string>Players</string>
</property>
@@ -260,7 +260,7 @@
</widget>
</item>
<item>
- <widget class="QGroupBox" name="gridGroupBox">
+ <widget class="QGroupBox" name="gridGroupBox_2">
<property name="title">
<string>Handheld</string>
</property>
@@ -332,7 +332,7 @@
</widget>
</item>
<item>
- <widget class="QGroupBox" name="gridGroupBox">
+ <widget class="QGroupBox" name="gridGroupBox_3">
<property name="title">
<string>Other</string>
</property>
diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp
index c5a245ebe..916baccc1 100644
--- a/src/yuzu/configuration/configure_input_player.cpp
+++ b/src/yuzu/configuration/configure_input_player.cpp
@@ -45,7 +45,7 @@ static QString GetKeyName(int key_code) {
case Qt::Key_Alt:
return QObject::tr("Alt");
case Qt::Key_Meta:
- return "";
+ return {};
default:
return QKeySequence(key_code).toString();
}
@@ -65,46 +65,70 @@ static void SetAnalogButton(const Common::ParamPackage& input_param,
static QString ButtonToText(const Common::ParamPackage& param) {
if (!param.Has("engine")) {
return QObject::tr("[not set]");
- } else if (param.Get("engine", "") == "keyboard") {
+ }
+
+ if (param.Get("engine", "") == "keyboard") {
return GetKeyName(param.Get("code", 0));
- } else if (param.Get("engine", "") == "sdl") {
+ }
+
+ if (param.Get("engine", "") == "sdl") {
if (param.Has("hat")) {
- return QString(QObject::tr("Hat %1 %2"))
- .arg(param.Get("hat", "").c_str(), param.Get("direction", "").c_str());
+ 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")) {
- return QString(QObject::tr("Axis %1%2"))
- .arg(param.Get("axis", "").c_str(), param.Get("direction", "").c_str());
+ 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")) {
- return QString(QObject::tr("Button %1")).arg(param.Get("button", "").c_str());
+ const QString button_str = QString::fromStdString(param.Get("button", ""));
+
+ return QObject::tr("Button %1").arg(button_str);
}
- return QString();
- } else {
- return QObject::tr("[unknown]");
+
+ return {};
}
-};
+
+ return QObject::tr("[unknown]");
+}
static QString AnalogToText(const Common::ParamPackage& param, const std::string& dir) {
if (!param.Has("engine")) {
return QObject::tr("[not set]");
- } else if (param.Get("engine", "") == "analog_from_button") {
+ }
+
+ if (param.Get("engine", "") == "analog_from_button") {
return ButtonToText(Common::ParamPackage{param.Get(dir, "")});
- } else if (param.Get("engine", "") == "sdl") {
+ }
+
+ if (param.Get("engine", "") == "sdl") {
if (dir == "modifier") {
- return QString(QObject::tr("[unused]"));
+ return QObject::tr("[unused]");
}
if (dir == "left" || dir == "right") {
- return QString(QObject::tr("Axis %1")).arg(param.Get("axis_x", "").c_str());
- } else if (dir == "up" || dir == "down") {
- return QString(QObject::tr("Axis %1")).arg(param.Get("axis_y", "").c_str());
+ const QString axis_x_str = QString::fromStdString(param.Get("axis_x", ""));
+
+ return QObject::tr("Axis %1").arg(axis_x_str);
}
- return QString();
- } else {
- return QObject::tr("[unknown]");
+
+ 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);
+ }
+
+ return {};
}
-};
+
+ return QObject::tr("[unknown]");
+}
ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_index, bool debug)
: QDialog(parent), ui(std::make_unique<Ui::ConfigureInputPlayer>()), player_index(player_index),
@@ -214,47 +238,51 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
analog_map_stick = {ui->buttonLStickAnalog, ui->buttonRStickAnalog};
for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) {
- if (!button_map[button_id])
+ auto* const button = button_map[button_id];
+ if (button == nullptr) {
continue;
- button_map[button_id]->setContextMenuPolicy(Qt::CustomContextMenu);
- connect(button_map[button_id], &QPushButton::released, [=]() {
- handleClick(
+ }
+
+ button->setContextMenuPolicy(Qt::CustomContextMenu);
+ connect(button, &QPushButton::released, [=] {
+ HandleClick(
button_map[button_id],
[=](const Common::ParamPackage& params) { buttons_param[button_id] = params; },
InputCommon::Polling::DeviceType::Button);
});
- connect(button_map[button_id], &QPushButton::customContextMenuRequested,
- [=](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.addAction(tr("Restore Default"), [&] {
- buttons_param[button_id] = Common::ParamPackage{
- InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])};
- button_map[button_id]->setText(ButtonToText(buttons_param[button_id]));
- });
- context_menu.exec(button_map[button_id]->mapToGlobal(menu_location));
- });
+ connect(button, &QPushButton::customContextMenuRequested, [=](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.addAction(tr("Restore Default"), [&] {
+ buttons_param[button_id] = Common::ParamPackage{
+ InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])};
+ button_map[button_id]->setText(ButtonToText(buttons_param[button_id]));
+ });
+ context_menu.exec(button_map[button_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++) {
- if (!analog_map_buttons[analog_id][sub_button_id])
+ auto* const analog_button = analog_map_buttons[analog_id][sub_button_id];
+ if (analog_button == nullptr) {
continue;
- analog_map_buttons[analog_id][sub_button_id]->setContextMenuPolicy(
- Qt::CustomContextMenu);
- connect(analog_map_buttons[analog_id][sub_button_id], &QPushButton::released, [=]() {
- handleClick(analog_map_buttons[analog_id][sub_button_id],
+ }
+
+ analog_button->setContextMenuPolicy(Qt::CustomContextMenu);
+ connect(analog_button, &QPushButton::released, [=]() {
+ HandleClick(analog_map_buttons[analog_id][sub_button_id],
[=](const Common::ParamPackage& params) {
SetAnalogButton(params, analogs_param[analog_id],
analog_sub_buttons[sub_button_id]);
},
InputCommon::Polling::DeviceType::Button);
});
- connect(analog_map_buttons[analog_id][sub_button_id],
- &QPushButton::customContextMenuRequested, [=](const QPoint& menu_location) {
+ connect(analog_button, &QPushButton::customContextMenuRequested,
+ [=](const QPoint& menu_location) {
QMenu context_menu;
context_menu.addAction(tr("Clear"), [&] {
analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]);
@@ -272,11 +300,11 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
menu_location));
});
}
- connect(analog_map_stick[analog_id], &QPushButton::released, [=]() {
+ connect(analog_map_stick[analog_id], &QPushButton::released, [=] {
QMessageBox::information(this, tr("Information"),
tr("After pressing OK, first move your joystick horizontally, "
"and then vertically."));
- handleClick(
+ HandleClick(
analog_map_stick[analog_id],
[=](const Common::ParamPackage& params) { analogs_param[analog_id] = params; },
InputCommon::Polling::DeviceType::Analog);
@@ -284,17 +312,17 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
}
connect(ui->buttonClearAll, &QPushButton::released, [this] { ClearAll(); });
- connect(ui->buttonRestoreDefaults, &QPushButton::released, [this]() { restoreDefaults(); });
+ connect(ui->buttonRestoreDefaults, &QPushButton::released, [this] { RestoreDefaults(); });
timeout_timer->setSingleShot(true);
- connect(timeout_timer.get(), &QTimer::timeout, [this]() { setPollingResult({}, true); });
+ connect(timeout_timer.get(), &QTimer::timeout, [this] { SetPollingResult({}, true); });
- connect(poll_timer.get(), &QTimer::timeout, [this]() {
+ 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);
+ SetPollingResult(params, false);
return;
}
}
@@ -312,8 +340,8 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
[this, i] { OnControllerButtonClick(static_cast<int>(i)); });
}
- this->loadConfiguration();
- this->resize(0, 0);
+ LoadConfiguration();
+ resize(0, 0);
// TODO(wwylele): enable this when we actually emulate it
ui->buttonHome->setEnabled(false);
@@ -321,7 +349,7 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
ConfigureInputPlayer::~ConfigureInputPlayer() = default;
-void ConfigureInputPlayer::applyConfiguration() {
+void ConfigureInputPlayer::ApplyConfiguration() {
auto& buttons =
debug ? Settings::values.debug_pad_buttons : Settings::values.players[player_index].buttons;
auto& analogs =
@@ -345,16 +373,29 @@ void ConfigureInputPlayer::applyConfiguration() {
Settings::values.players[player_index].button_color_right = colors[3];
}
+void ConfigureInputPlayer::changeEvent(QEvent* event) {
+ if (event->type() == QEvent::LanguageChange) {
+ RetranslateUI();
+ }
+
+ QDialog::changeEvent(event);
+}
+
+void ConfigureInputPlayer::RetranslateUI() {
+ ui->retranslateUi(this);
+ UpdateButtonLabels();
+}
+
void ConfigureInputPlayer::OnControllerButtonClick(int i) {
const QColor new_bg_color = QColorDialog::getColor(controller_colors[i]);
if (!new_bg_color.isValid())
return;
controller_colors[i] = new_bg_color;
controller_color_buttons[i]->setStyleSheet(
- QString("QPushButton { background-color: %1 }").arg(controller_colors[i].name()));
+ QStringLiteral("QPushButton { background-color: %1 }").arg(controller_colors[i].name()));
}
-void ConfigureInputPlayer::loadConfiguration() {
+void ConfigureInputPlayer::LoadConfiguration() {
if (debug) {
std::transform(Settings::values.debug_pad_buttons.begin(),
Settings::values.debug_pad_buttons.end(), buttons_param.begin(),
@@ -371,7 +412,7 @@ void ConfigureInputPlayer::loadConfiguration() {
[](const std::string& str) { return Common::ParamPackage(str); });
}
- updateButtonLabels();
+ UpdateButtonLabels();
if (debug)
return;
@@ -388,11 +429,12 @@ void ConfigureInputPlayer::loadConfiguration() {
for (std::size_t i = 0; i < colors.size(); ++i) {
controller_color_buttons[i]->setStyleSheet(
- QString("QPushButton { background-color: %1 }").arg(controller_colors[i].name()));
+ QStringLiteral("QPushButton { background-color: %1 }")
+ .arg(controller_colors[i].name()));
}
}
-void ConfigureInputPlayer::restoreDefaults() {
+void ConfigureInputPlayer::RestoreDefaults() {
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])};
@@ -405,42 +447,54 @@ void ConfigureInputPlayer::restoreDefaults() {
SetAnalogButton(params, analogs_param[analog_id], analog_sub_buttons[sub_button_id]);
}
}
- updateButtonLabels();
+ UpdateButtonLabels();
}
void ConfigureInputPlayer::ClearAll() {
for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) {
- if (button_map[button_id] && button_map[button_id]->isEnabled())
- buttons_param[button_id].Clear();
+ const auto* const button = button_map[button_id];
+ if (button == nullptr || !button->isEnabled()) {
+ continue;
+ }
+
+ buttons_param[button_id].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++) {
- if (analog_map_buttons[analog_id][sub_button_id] &&
- analog_map_buttons[analog_id][sub_button_id]->isEnabled())
- analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]);
+ const auto* const analog_button = analog_map_buttons[analog_id][sub_button_id];
+ if (analog_button == nullptr || !analog_button->isEnabled()) {
+ continue;
+ }
+
+ analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]);
}
}
- updateButtonLabels();
+ UpdateButtonLabels();
}
-void ConfigureInputPlayer::updateButtonLabels() {
+void ConfigureInputPlayer::UpdateButtonLabels() {
for (int button = 0; button < Settings::NativeButton::NumButtons; button++) {
button_map[button]->setText(ButtonToText(buttons_param[button]));
}
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++) {
- if (analog_map_buttons[analog_id][sub_button_id]) {
- analog_map_buttons[analog_id][sub_button_id]->setText(
- AnalogToText(analogs_param[analog_id], analog_sub_buttons[sub_button_id]));
+ auto* const analog_button = analog_map_buttons[analog_id][sub_button_id];
+
+ if (analog_button == nullptr) {
+ continue;
}
+
+ analog_button->setText(
+ AnalogToText(analogs_param[analog_id], analog_sub_buttons[sub_button_id]));
}
analog_map_stick[analog_id]->setText(tr("Set Analog Stick"));
}
}
-void ConfigureInputPlayer::handleClick(
+void ConfigureInputPlayer::HandleClick(
QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter,
InputCommon::Polling::DeviceType type) {
button->setText(tr("[press key]"));
@@ -468,7 +522,7 @@ void ConfigureInputPlayer::handleClick(
poll_timer->start(200); // Check for new inputs every 200ms
}
-void ConfigureInputPlayer::setPollingResult(const Common::ParamPackage& params, bool abort) {
+void ConfigureInputPlayer::SetPollingResult(const Common::ParamPackage& params, bool abort) {
releaseKeyboard();
releaseMouse();
timeout_timer->stop();
@@ -481,22 +535,23 @@ void ConfigureInputPlayer::setPollingResult(const Common::ParamPackage& params,
(*input_setter)(params);
}
- updateButtonLabels();
+ UpdateButtonLabels();
input_setter = std::nullopt;
}
void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) {
- if (!input_setter || !event)
+ if (!input_setter || !event) {
return;
+ }
if (event->key() != Qt::Key_Escape) {
if (want_keyboard_keys) {
- setPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())},
+ 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);
+ SetPollingResult({}, true);
}
diff --git a/src/yuzu/configuration/configure_input_player.h b/src/yuzu/configuration/configure_input_player.h
index ade8d4435..c66027651 100644
--- a/src/yuzu/configuration/configure_input_player.h
+++ b/src/yuzu/configuration/configure_input_player.h
@@ -38,28 +38,31 @@ public:
~ConfigureInputPlayer() override;
/// Save all button configurations to settings file
- void applyConfiguration();
+ void ApplyConfiguration();
private:
+ void changeEvent(QEvent* event) override;
+ void RetranslateUI();
+
void OnControllerButtonClick(int i);
/// Load configuration settings.
- void loadConfiguration();
+ void LoadConfiguration();
/// Restore all buttons to their default values.
- void restoreDefaults();
+ void RestoreDefaults();
/// Clear all input configuration
void ClearAll();
/// Update UI to reflect current configuration.
- void updateButtonLabels();
+ void UpdateButtonLabels();
/// Called when the button was pressed.
- void handleClick(QPushButton* button,
+ void HandleClick(QPushButton* button,
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);
+ void SetPollingResult(const Common::ParamPackage& params, bool abort);
/// Handle key press events.
void keyPressEvent(QKeyEvent* event) override;
diff --git a/src/yuzu/configuration/configure_input_simple.cpp b/src/yuzu/configuration/configure_input_simple.cpp
index 07d71e9d1..864803ea3 100644
--- a/src/yuzu/configuration/configure_input_simple.cpp
+++ b/src/yuzu/configuration/configure_input_simple.cpp
@@ -15,12 +15,12 @@ namespace {
template <typename Dialog, typename... Args>
void CallConfigureDialog(ConfigureInputSimple* caller, Args&&... args) {
- caller->applyConfiguration();
+ caller->ApplyConfiguration();
Dialog dialog(caller, std::forward<Args>(args)...);
const auto res = dialog.exec();
if (res == QDialog::Accepted) {
- dialog.applyConfiguration();
+ dialog.ApplyConfiguration();
}
}
@@ -103,27 +103,41 @@ ConfigureInputSimple::ConfigureInputSimple(QWidget* parent)
&ConfigureInputSimple::OnSelectProfile);
connect(ui->profile_configure, &QPushButton::pressed, this, &ConfigureInputSimple::OnConfigure);
- this->loadConfiguration();
+ LoadConfiguration();
}
ConfigureInputSimple::~ConfigureInputSimple() = default;
-void ConfigureInputSimple::applyConfiguration() {
+void ConfigureInputSimple::ApplyConfiguration() {
auto index = ui->profile_combobox->currentIndex();
// Make the stored index for "Custom" very large so that if new profiles are added it
// doesn't change.
- if (index >= static_cast<int>(INPUT_PROFILES.size() - 1))
+ if (index >= static_cast<int>(INPUT_PROFILES.size() - 1)) {
index = std::numeric_limits<int>::max();
+ }
UISettings::values.profile_index = index;
}
-void ConfigureInputSimple::loadConfiguration() {
+void ConfigureInputSimple::changeEvent(QEvent* event) {
+ if (event->type() == QEvent::LanguageChange) {
+ RetranslateUI();
+ }
+
+ QWidget::changeEvent(event);
+}
+
+void ConfigureInputSimple::RetranslateUI() {
+ ui->retranslateUi(this);
+}
+
+void ConfigureInputSimple::LoadConfiguration() {
const auto index = UISettings::values.profile_index;
- if (index >= static_cast<int>(INPUT_PROFILES.size()) || index < 0)
+ if (index >= static_cast<int>(INPUT_PROFILES.size()) || index < 0) {
ui->profile_combobox->setCurrentIndex(static_cast<int>(INPUT_PROFILES.size() - 1));
- else
+ } else {
ui->profile_combobox->setCurrentIndex(index);
+ }
}
void ConfigureInputSimple::OnSelectProfile(int index) {
diff --git a/src/yuzu/configuration/configure_input_simple.h b/src/yuzu/configuration/configure_input_simple.h
index 5b6b69994..bb5050224 100644
--- a/src/yuzu/configuration/configure_input_simple.h
+++ b/src/yuzu/configuration/configure_input_simple.h
@@ -27,11 +27,14 @@ public:
~ConfigureInputSimple() override;
/// Save all button configurations to settings file
- void applyConfiguration();
+ void ApplyConfiguration();
private:
+ void changeEvent(QEvent* event) override;
+ void RetranslateUI();
+
/// Load configuration settings.
- void loadConfiguration();
+ void LoadConfiguration();
void OnSelectProfile(int index);
void OnConfigure();
diff --git a/src/yuzu/configuration/configure_mouse_advanced.cpp b/src/yuzu/configuration/configure_mouse_advanced.cpp
index ef857035e..b7305e653 100644
--- a/src/yuzu/configuration/configure_mouse_advanced.cpp
+++ b/src/yuzu/configuration/configure_mouse_advanced.cpp
@@ -25,7 +25,7 @@ static QString GetKeyName(int key_code) {
case Qt::Key_Alt:
return QObject::tr("Alt");
case Qt::Key_Meta:
- return "";
+ return {};
default:
return QKeySequence(key_code).toString();
}
@@ -34,24 +34,36 @@ static QString GetKeyName(int key_code) {
static QString ButtonToText(const Common::ParamPackage& param) {
if (!param.Has("engine")) {
return QObject::tr("[not set]");
- } else if (param.Get("engine", "") == "keyboard") {
+ }
+
+ if (param.Get("engine", "") == "keyboard") {
return GetKeyName(param.Get("code", 0));
- } else if (param.Get("engine", "") == "sdl") {
+ }
+
+ if (param.Get("engine", "") == "sdl") {
if (param.Has("hat")) {
- return QString(QObject::tr("Hat %1 %2"))
- .arg(param.Get("hat", "").c_str(), param.Get("direction", "").c_str());
+ 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")) {
- return QString(QObject::tr("Axis %1%2"))
- .arg(param.Get("axis", "").c_str(), param.Get("direction", "").c_str());
+ 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")) {
- return QString(QObject::tr("Button %1")).arg(param.Get("button", "").c_str());
+ const QString button_str = QString::fromStdString(param.Get("button", ""));
+
+ return QObject::tr("Button %1").arg(button_str);
}
- return QString();
- } else {
- return QObject::tr("[unknown]");
+ return {};
}
+
+ return QObject::tr("[unknown]");
}
ConfigureMouseAdvanced::ConfigureMouseAdvanced(QWidget* parent)
@@ -65,93 +77,108 @@ ConfigureMouseAdvanced::ConfigureMouseAdvanced(QWidget* parent)
};
for (int button_id = 0; button_id < Settings::NativeMouseButton::NumMouseButtons; button_id++) {
- if (!button_map[button_id])
+ auto* const button = button_map[button_id];
+ if (button == nullptr) {
continue;
- button_map[button_id]->setContextMenuPolicy(Qt::CustomContextMenu);
- connect(button_map[button_id], &QPushButton::released, [=]() {
- handleClick(
+ }
+
+ button->setContextMenuPolicy(Qt::CustomContextMenu);
+ connect(button, &QPushButton::released, [=] {
+ HandleClick(
button_map[button_id],
[=](const Common::ParamPackage& params) { buttons_param[button_id] = params; },
InputCommon::Polling::DeviceType::Button);
});
- connect(button_map[button_id], &QPushButton::customContextMenuRequested,
- [=](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.addAction(tr("Restore Default"), [&] {
- buttons_param[button_id] =
- Common::ParamPackage{InputCommon::GenerateKeyboardParam(
- Config::default_mouse_buttons[button_id])};
- button_map[button_id]->setText(ButtonToText(buttons_param[button_id]));
- });
- context_menu.exec(button_map[button_id]->mapToGlobal(menu_location));
- });
+ connect(button, &QPushButton::customContextMenuRequested, [=](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.addAction(tr("Restore Default"), [&] {
+ buttons_param[button_id] = Common::ParamPackage{
+ InputCommon::GenerateKeyboardParam(Config::default_mouse_buttons[button_id])};
+ button_map[button_id]->setText(ButtonToText(buttons_param[button_id]));
+ });
+ context_menu.exec(button_map[button_id]->mapToGlobal(menu_location));
+ });
}
connect(ui->buttonClearAll, &QPushButton::released, [this] { ClearAll(); });
- connect(ui->buttonRestoreDefaults, &QPushButton::released, [this]() { restoreDefaults(); });
+ connect(ui->buttonRestoreDefaults, &QPushButton::released, [this] { RestoreDefaults(); });
timeout_timer->setSingleShot(true);
- connect(timeout_timer.get(), &QTimer::timeout, [this]() { setPollingResult({}, true); });
+ connect(timeout_timer.get(), &QTimer::timeout, [this] { SetPollingResult({}, true); });
- connect(poll_timer.get(), &QTimer::timeout, [this]() {
+ 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);
+ SetPollingResult(params, false);
return;
}
}
});
- loadConfiguration();
+ LoadConfiguration();
resize(0, 0);
}
ConfigureMouseAdvanced::~ConfigureMouseAdvanced() = default;
-void ConfigureMouseAdvanced::applyConfiguration() {
+void ConfigureMouseAdvanced::ApplyConfiguration() {
std::transform(buttons_param.begin(), buttons_param.end(),
Settings::values.mouse_buttons.begin(),
[](const Common::ParamPackage& param) { return param.Serialize(); });
}
-void ConfigureMouseAdvanced::loadConfiguration() {
+void ConfigureMouseAdvanced::LoadConfiguration() {
std::transform(Settings::values.mouse_buttons.begin(), Settings::values.mouse_buttons.end(),
buttons_param.begin(),
[](const std::string& str) { return Common::ParamPackage(str); });
- updateButtonLabels();
+ UpdateButtonLabels();
+}
+
+void ConfigureMouseAdvanced::changeEvent(QEvent* event) {
+ if (event->type() == QEvent::LanguageChange) {
+ RetranslateUI();
+ }
+
+ QDialog::changeEvent(event);
+}
+
+void ConfigureMouseAdvanced::RetranslateUI() {
+ ui->retranslateUi(this);
}
-void ConfigureMouseAdvanced::restoreDefaults() {
+void ConfigureMouseAdvanced::RestoreDefaults() {
for (int button_id = 0; button_id < Settings::NativeMouseButton::NumMouseButtons; button_id++) {
buttons_param[button_id] = Common::ParamPackage{
InputCommon::GenerateKeyboardParam(Config::default_mouse_buttons[button_id])};
}
- updateButtonLabels();
+ UpdateButtonLabels();
}
void ConfigureMouseAdvanced::ClearAll() {
for (int i = 0; i < Settings::NativeMouseButton::NumMouseButtons; ++i) {
- if (button_map[i] && button_map[i]->isEnabled())
+ const auto* const button = button_map[i];
+ if (button != nullptr && button->isEnabled()) {
buttons_param[i].Clear();
+ }
}
- updateButtonLabels();
+ UpdateButtonLabels();
}
-void ConfigureMouseAdvanced::updateButtonLabels() {
+void ConfigureMouseAdvanced::UpdateButtonLabels() {
for (int button = 0; button < Settings::NativeMouseButton::NumMouseButtons; button++) {
button_map[button]->setText(ButtonToText(buttons_param[button]));
}
}
-void ConfigureMouseAdvanced::handleClick(
+void ConfigureMouseAdvanced::HandleClick(
QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter,
InputCommon::Polling::DeviceType type) {
button->setText(tr("[press key]"));
@@ -179,7 +206,7 @@ void ConfigureMouseAdvanced::handleClick(
poll_timer->start(200); // Check for new inputs every 200ms
}
-void ConfigureMouseAdvanced::setPollingResult(const Common::ParamPackage& params, bool abort) {
+void ConfigureMouseAdvanced::SetPollingResult(const Common::ParamPackage& params, bool abort) {
releaseKeyboard();
releaseMouse();
timeout_timer->stop();
@@ -192,22 +219,23 @@ void ConfigureMouseAdvanced::setPollingResult(const Common::ParamPackage& params
(*input_setter)(params);
}
- updateButtonLabels();
+ UpdateButtonLabels();
input_setter = std::nullopt;
}
void ConfigureMouseAdvanced::keyPressEvent(QKeyEvent* event) {
- if (!input_setter || !event)
+ if (!input_setter || !event) {
return;
+ }
if (event->key() != Qt::Key_Escape) {
if (want_keyboard_keys) {
- setPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())},
+ 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);
+ SetPollingResult({}, true);
}
diff --git a/src/yuzu/configuration/configure_mouse_advanced.h b/src/yuzu/configuration/configure_mouse_advanced.h
index e04da4bf2..342b82412 100644
--- a/src/yuzu/configuration/configure_mouse_advanced.h
+++ b/src/yuzu/configuration/configure_mouse_advanced.h
@@ -25,26 +25,29 @@ public:
explicit ConfigureMouseAdvanced(QWidget* parent);
~ConfigureMouseAdvanced() override;
- void applyConfiguration();
+ void ApplyConfiguration();
private:
+ void changeEvent(QEvent* event) override;
+ void RetranslateUI();
+
/// Load configuration settings.
- void loadConfiguration();
+ void LoadConfiguration();
/// Restore all buttons to their default values.
- void restoreDefaults();
+ void RestoreDefaults();
/// Clear all input configuration
void ClearAll();
/// Update UI to reflect current configuration.
- void updateButtonLabels();
+ void UpdateButtonLabels();
/// Called when the button was pressed.
- void handleClick(QPushButton* button,
+ void HandleClick(QPushButton* button,
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);
+ void SetPollingResult(const Common::ParamPackage& params, bool abort);
/// Handle key press events.
void keyPressEvent(QKeyEvent* event) override;
diff --git a/src/yuzu/configuration/configure_per_general.cpp b/src/yuzu/configuration/configure_per_general.cpp
index 022b94609..90336e235 100644
--- a/src/yuzu/configuration/configure_per_general.cpp
+++ b/src/yuzu/configuration/configure_per_general.cpp
@@ -13,6 +13,8 @@
#include <QTimer>
#include <QTreeView>
+#include "common/common_paths.h"
+#include "common/file_util.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/patch_manager.h"
#include "core/file_sys/xts_archive.h"
@@ -65,12 +67,12 @@ ConfigurePerGameGeneral::ConfigurePerGameGeneral(QWidget* parent, u64 title_id)
connect(item_model, &QStandardItemModel::itemChanged,
[] { UISettings::values.is_game_list_reload_pending.exchange(true); });
- this->loadConfiguration();
+ LoadConfiguration();
}
ConfigurePerGameGeneral::~ConfigurePerGameGeneral() = default;
-void ConfigurePerGameGeneral::applyConfiguration() {
+void ConfigurePerGameGeneral::ApplyConfiguration() {
std::vector<std::string> disabled_addons;
for (const auto& item : list_items) {
@@ -79,24 +81,44 @@ void ConfigurePerGameGeneral::applyConfiguration() {
disabled_addons.push_back(item.front()->text().toStdString());
}
+ auto current = Settings::values.disabled_addons[title_id];
+ std::sort(disabled_addons.begin(), disabled_addons.end());
+ std::sort(current.begin(), current.end());
+ if (disabled_addons != current) {
+ FileUtil::Delete(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP +
+ "game_list" + DIR_SEP + fmt::format("{:016X}.pv.txt", title_id));
+ }
+
Settings::values.disabled_addons[title_id] = disabled_addons;
}
-void ConfigurePerGameGeneral::loadFromFile(FileSys::VirtualFile file) {
+void ConfigurePerGameGeneral::changeEvent(QEvent* event) {
+ if (event->type() == QEvent::LanguageChange) {
+ RetranslateUI();
+ }
+
+ QDialog::changeEvent(event);
+}
+
+void ConfigurePerGameGeneral::RetranslateUI() {
+ ui->retranslateUi(this);
+}
+
+void ConfigurePerGameGeneral::LoadFromFile(FileSys::VirtualFile file) {
this->file = std::move(file);
- this->loadConfiguration();
+ LoadConfiguration();
}
-void ConfigurePerGameGeneral::loadConfiguration() {
- if (file == nullptr)
+void ConfigurePerGameGeneral::LoadConfiguration() {
+ if (file == nullptr) {
return;
+ }
- const auto loader = Loader::GetLoader(file);
-
- ui->display_title_id->setText(fmt::format("{:016X}", title_id).c_str());
+ ui->display_title_id->setText(QString::fromStdString(fmt::format("{:016X}", title_id)));
FileSys::PatchManager pm{title_id};
const auto control = pm.GetControlMetadata();
+ const auto loader = Loader::GetLoader(file);
if (control.first != nullptr) {
ui->display_version->setText(QString::fromStdString(control.first->GetVersionString()));
@@ -142,8 +164,10 @@ void ConfigurePerGameGeneral::loadConfiguration() {
const auto& disabled = Settings::values.disabled_addons[title_id];
for (const auto& patch : pm.GetPatchVersionNames(update_raw)) {
- QStandardItem* first_item = new QStandardItem;
- const auto name = QString::fromStdString(patch.first).replace("[D] ", "");
+ const auto name =
+ QString::fromStdString(patch.first).replace(QStringLiteral("[D] "), QString{});
+
+ auto* const first_item = new QStandardItem;
first_item->setText(name);
first_item->setCheckable(true);
diff --git a/src/yuzu/configuration/configure_per_general.h b/src/yuzu/configuration/configure_per_general.h
index f8a7d5326..a3b2cdeff 100644
--- a/src/yuzu/configuration/configure_per_general.h
+++ b/src/yuzu/configuration/configure_per_general.h
@@ -30,11 +30,16 @@ public:
~ConfigurePerGameGeneral() override;
/// Save all button configurations to settings file
- void applyConfiguration();
+ void ApplyConfiguration();
- void loadFromFile(FileSys::VirtualFile file);
+ void LoadFromFile(FileSys::VirtualFile file);
private:
+ void changeEvent(QEvent* event) override;
+ void RetranslateUI();
+
+ void LoadConfiguration();
+
std::unique_ptr<Ui::ConfigurePerGameGeneral> ui;
FileSys::VirtualFile file;
u64 title_id;
@@ -45,6 +50,4 @@ private:
QGraphicsScene* scene;
std::vector<QList<QStandardItem*>> list_items;
-
- void loadConfiguration();
};
diff --git a/src/yuzu/configuration/configure_profile_manager.cpp b/src/yuzu/configuration/configure_profile_manager.cpp
index 41663e39a..c90f4cdd8 100644
--- a/src/yuzu/configuration/configure_profile_manager.cpp
+++ b/src/yuzu/configuration/configure_profile_manager.cpp
@@ -33,14 +33,13 @@ constexpr std::array<u8, 107> backup_jpeg{
0x01, 0x01, 0x00, 0x00, 0x3f, 0x00, 0xd2, 0xcf, 0x20, 0xff, 0xd9,
};
-QString GetImagePath(Service::Account::UUID uuid) {
+QString GetImagePath(Common::UUID uuid) {
const auto path = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
"/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg";
return QString::fromStdString(path);
}
-QString GetAccountUsername(const Service::Account::ProfileManager& manager,
- Service::Account::UUID uuid) {
+QString GetAccountUsername(const Service::Account::ProfileManager& manager, Common::UUID uuid) {
Service::Account::ProfileBase profile;
if (!manager.GetProfileBase(uuid, profile)) {
return {};
@@ -51,14 +50,14 @@ QString GetAccountUsername(const Service::Account::ProfileManager& manager,
return QString::fromStdString(text);
}
-QString FormatUserEntryText(const QString& username, Service::Account::UUID uuid) {
+QString FormatUserEntryText(const QString& username, Common::UUID uuid) {
return ConfigureProfileManager::tr("%1\n%2",
"%1 is the profile username, %2 is the formatted UUID (e.g. "
"00112233-4455-6677-8899-AABBCCDDEEFF))")
.arg(username, QString::fromStdString(uuid.FormatSwitch()));
}
-QPixmap GetIcon(Service::Account::UUID uuid) {
+QPixmap GetIcon(Common::UUID uuid) {
QPixmap icon{GetImagePath(uuid)};
if (!icon) {
@@ -81,11 +80,10 @@ ConfigureProfileManager ::ConfigureProfileManager(QWidget* parent)
profile_manager(std::make_unique<Service::Account::ProfileManager>()) {
ui->setupUi(this);
- layout = new QVBoxLayout;
tree_view = new QTreeView;
item_model = new QStandardItemModel(tree_view);
+ item_model->insertColumns(0, 1);
tree_view->setModel(item_model);
-
tree_view->setAlternatingRowColors(true);
tree_view->setSelectionMode(QHeaderView::SingleSelection);
tree_view->setSelectionBehavior(QHeaderView::SelectRows);
@@ -97,13 +95,11 @@ ConfigureProfileManager ::ConfigureProfileManager(QWidget* parent)
tree_view->setIconSize({64, 64});
tree_view->setContextMenuPolicy(Qt::NoContextMenu);
- item_model->insertColumns(0, 1);
- item_model->setHeaderData(0, Qt::Horizontal, "Users");
-
// 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.
qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>");
+ layout = new QVBoxLayout;
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(0);
layout->addWidget(tree_view);
@@ -120,12 +116,26 @@ ConfigureProfileManager ::ConfigureProfileManager(QWidget* parent)
scene = new QGraphicsScene;
ui->current_user_icon->setScene(scene);
- this->setConfiguration();
+ SetConfiguration();
+ RetranslateUI();
}
ConfigureProfileManager::~ConfigureProfileManager() = default;
-void ConfigureProfileManager::setConfiguration() {
+void ConfigureProfileManager::changeEvent(QEvent* event) {
+ if (event->type() == QEvent::LanguageChange) {
+ RetranslateUI();
+ }
+
+ QWidget::changeEvent(event);
+}
+
+void ConfigureProfileManager::RetranslateUI() {
+ ui->retranslateUi(this);
+ item_model->setHeaderData(0, Qt::Horizontal, tr("Users"));
+}
+
+void ConfigureProfileManager::SetConfiguration() {
enabled = !Core::System::GetInstance().IsPoweredOn();
item_model->removeRows(0, item_model->rowCount());
list_items.clear();
@@ -165,9 +175,10 @@ void ConfigureProfileManager::UpdateCurrentUser() {
ui->current_user_username->setText(username);
}
-void ConfigureProfileManager::applyConfiguration() {
- if (!enabled)
+void ConfigureProfileManager::ApplyConfiguration() {
+ if (!enabled) {
return;
+ }
Settings::Apply();
}
@@ -190,7 +201,7 @@ void ConfigureProfileManager::AddUser() {
return;
}
- const auto uuid = Service::Account::UUID::Generate();
+ const auto uuid = Common::UUID::Generate();
profile_manager->CreateNewUser(uuid, username.toStdString());
item_model->appendRow(new QStandardItem{GetIcon(uuid), FormatUserEntryText(username, uuid)});
diff --git a/src/yuzu/configuration/configure_profile_manager.h b/src/yuzu/configuration/configure_profile_manager.h
index 7fe95a2a8..0a9bca2a6 100644
--- a/src/yuzu/configuration/configure_profile_manager.h
+++ b/src/yuzu/configuration/configure_profile_manager.h
@@ -30,10 +30,14 @@ public:
explicit ConfigureProfileManager(QWidget* parent = nullptr);
~ConfigureProfileManager() override;
- void applyConfiguration();
- void setConfiguration();
+ void ApplyConfiguration();
private:
+ void changeEvent(QEvent* event) override;
+ void RetranslateUI();
+
+ void SetConfiguration();
+
void PopulateUserList();
void UpdateCurrentUser();
diff --git a/src/yuzu/configuration/configure_system.cpp b/src/yuzu/configuration/configure_system.cpp
index 10645a2b3..e1b52f8d9 100644
--- a/src/yuzu/configuration/configure_system.cpp
+++ b/src/yuzu/configuration/configure_system.cpp
@@ -16,49 +16,43 @@
#include "ui_configure_system.h"
#include "yuzu/configuration/configure_system.h"
-namespace {
-constexpr std::array<int, 12> days_in_month = {{
- 31,
- 29,
- 31,
- 30,
- 31,
- 30,
- 31,
- 31,
- 30,
- 31,
- 30,
- 31,
-}};
-} // Anonymous namespace
-
ConfigureSystem::ConfigureSystem(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureSystem) {
ui->setupUi(this);
- connect(ui->combo_birthmonth,
- static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
- &ConfigureSystem::UpdateBirthdayComboBox);
connect(ui->button_regenerate_console_id, &QPushButton::clicked, this,
&ConfigureSystem::RefreshConsoleID);
connect(ui->rng_seed_checkbox, &QCheckBox::stateChanged, this, [this](bool checked) {
ui->rng_seed_edit->setEnabled(checked);
- if (!checked)
+ if (!checked) {
ui->rng_seed_edit->setText(QStringLiteral("00000000"));
+ }
});
connect(ui->custom_rtc_checkbox, &QCheckBox::stateChanged, this, [this](bool checked) {
ui->custom_rtc_edit->setEnabled(checked);
- if (!checked)
+ if (!checked) {
ui->custom_rtc_edit->setDateTime(QDateTime::currentDateTime());
+ }
});
- this->setConfiguration();
+ SetConfiguration();
}
ConfigureSystem::~ConfigureSystem() = default;
-void ConfigureSystem::setConfiguration() {
+void ConfigureSystem::changeEvent(QEvent* event) {
+ if (event->type() == QEvent::LanguageChange) {
+ RetranslateUI();
+ }
+
+ QWidget::changeEvent(event);
+}
+
+void ConfigureSystem::RetranslateUI() {
+ ui->retranslateUi(this);
+}
+
+void ConfigureSystem::SetConfiguration() {
enabled = !Core::System::GetInstance().IsPoweredOn();
ui->combo_language->setCurrentIndex(Settings::values.language_index);
@@ -66,8 +60,9 @@ void ConfigureSystem::setConfiguration() {
ui->rng_seed_checkbox->setChecked(Settings::values.rng_seed.has_value());
ui->rng_seed_edit->setEnabled(Settings::values.rng_seed.has_value());
- const auto rng_seed =
- QString("%1").arg(Settings::values.rng_seed.value_or(0), 8, 16, QLatin1Char{'0'}).toUpper();
+ const auto rng_seed = QStringLiteral("%1")
+ .arg(Settings::values.rng_seed.value_or(0), 8, 16, QLatin1Char{'0'})
+ .toUpper();
ui->rng_seed_edit->setText(rng_seed);
ui->custom_rtc_checkbox->setChecked(Settings::values.custom_rtc.has_value());
@@ -80,49 +75,27 @@ void ConfigureSystem::setConfiguration() {
void ConfigureSystem::ReadSystemSettings() {}
-void ConfigureSystem::applyConfiguration() {
- if (!enabled)
+void ConfigureSystem::ApplyConfiguration() {
+ if (!enabled) {
return;
+ }
Settings::values.language_index = ui->combo_language->currentIndex();
- if (ui->rng_seed_checkbox->isChecked())
+ if (ui->rng_seed_checkbox->isChecked()) {
Settings::values.rng_seed = ui->rng_seed_edit->text().toULongLong(nullptr, 16);
- else
+ } else {
Settings::values.rng_seed = std::nullopt;
+ }
- if (ui->custom_rtc_checkbox->isChecked())
+ if (ui->custom_rtc_checkbox->isChecked()) {
Settings::values.custom_rtc =
std::chrono::seconds(ui->custom_rtc_edit->dateTime().toSecsSinceEpoch());
- else
+ } else {
Settings::values.custom_rtc = std::nullopt;
-
- Settings::Apply();
-}
-
-void ConfigureSystem::UpdateBirthdayComboBox(int birthmonth_index) {
- if (birthmonth_index < 0 || birthmonth_index >= 12)
- return;
-
- // store current day selection
- int birthday_index = ui->combo_birthday->currentIndex();
-
- // get number of days in the new selected month
- int days = days_in_month[birthmonth_index];
-
- // if the selected day is out of range,
- // reset it to 1st
- if (birthday_index < 0 || birthday_index >= days)
- birthday_index = 0;
-
- // update the day combo box
- ui->combo_birthday->clear();
- for (int i = 1; i <= days; ++i) {
- ui->combo_birthday->addItem(QString::number(i));
}
- // restore the day selection
- ui->combo_birthday->setCurrentIndex(birthday_index);
+ Settings::Apply();
}
void ConfigureSystem::RefreshConsoleID() {
@@ -133,8 +106,10 @@ void ConfigureSystem::RefreshConsoleID() {
"if you use an outdated config savegame. Continue?");
reply = QMessageBox::critical(this, tr("Warning"), warning_text,
QMessageBox::No | QMessageBox::Yes);
- if (reply == QMessageBox::No)
+ if (reply == QMessageBox::No) {
return;
+ }
+
u64 console_id{};
ui->label_console_id->setText(
tr("Console ID: 0x%1").arg(QString::number(console_id, 16).toUpper()));
diff --git a/src/yuzu/configuration/configure_system.h b/src/yuzu/configuration/configure_system.h
index cf1e54de5..1eab3781d 100644
--- a/src/yuzu/configuration/configure_system.h
+++ b/src/yuzu/configuration/configure_system.h
@@ -20,20 +20,21 @@ public:
explicit ConfigureSystem(QWidget* parent = nullptr);
~ConfigureSystem() override;
- void applyConfiguration();
- void setConfiguration();
+ void ApplyConfiguration();
private:
+ void changeEvent(QEvent* event) override;
+ void RetranslateUI();
+
+ void SetConfiguration();
+
void ReadSystemSettings();
- void UpdateBirthdayComboBox(int birthmonth_index);
void RefreshConsoleID();
std::unique_ptr<Ui::ConfigureSystem> ui;
bool enabled = false;
- int birthmonth = 0;
- int birthday = 0;
int language_index = 0;
int sound_index = 0;
};
diff --git a/src/yuzu/configuration/configure_system.ui b/src/yuzu/configuration/configure_system.ui
index 073327298..65745a2f8 100644
--- a/src/yuzu/configuration/configure_system.ui
+++ b/src/yuzu/configuration/configure_system.ui
@@ -22,14 +22,21 @@
<string>System Settings</string>
</property>
<layout class="QGridLayout" name="gridLayout">
- <item row="2" column="0">
+ <item row="1" column="0">
<widget class="QLabel" name="label_sound">
<property name="text">
<string>Sound output mode</string>
</property>
</widget>
</item>
- <item row="1" column="1">
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_console_id">
+ <property name="text">
+ <string>Console ID:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
<widget class="QComboBox" name="combo_language">
<property name="toolTip">
<string>Note: this can be overridden when region setting is auto-select</string>
@@ -121,108 +128,14 @@
</item>
</widget>
</item>
- <item row="0" column="1">
- <layout class="QHBoxLayout" name="horizontalLayout_birthday2">
- <item>
- <widget class="QComboBox" name="combo_birthmonth">
- <item>
- <property name="text">
- <string>January</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>February</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>March</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>April</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>May</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>June</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>July</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>August</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>September</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>October</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>November</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>December</string>
- </property>
- </item>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="combo_birthday"/>
- </item>
- </layout>
- </item>
- <item row="3" column="0">
- <widget class="QLabel" name="label_console_id">
- <property name="text">
- <string>Console ID:</string>
- </property>
- </widget>
- </item>
- <item row="0" column="0">
- <widget class="QLabel" name="label_birthday">
- <property name="text">
- <string>Birthday</string>
- </property>
- </widget>
- </item>
- <item row="3" column="1">
- <widget class="QPushButton" name="button_regenerate_console_id">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="layoutDirection">
- <enum>Qt::RightToLeft</enum>
- </property>
+ <item row="4" column="0">
+ <widget class="QCheckBox" name="rng_seed_checkbox">
<property name="text">
- <string>Regenerate</string>
+ <string>RNG Seed</string>
</property>
</widget>
</item>
- <item row="2" column="1">
+ <item row="1" column="1">
<widget class="QComboBox" name="combo_sound">
<item>
<property name="text">
@@ -241,49 +154,37 @@
</item>
</widget>
</item>
- <item row="5" column="0">
- <widget class="QCheckBox" name="rng_seed_checkbox">
- <property name="text">
- <string>RNG Seed</string>
- </property>
- </widget>
- </item>
- <item row="1" column="0">
+ <item row="0" column="0">
<widget class="QLabel" name="label_language">
<property name="text">
<string>Language</string>
</property>
</widget>
</item>
- <item row="5" column="1">
- <widget class="QLineEdit" name="rng_seed_edit">
+ <item row="2" column="1">
+ <widget class="QPushButton" name="button_regenerate_console_id">
<property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
- <property name="font">
- <font>
- <family>Lucida Console</family>
- </font>
- </property>
- <property name="inputMask">
- <string notr="true">HHHHHHHH</string>
+ <property name="layoutDirection">
+ <enum>Qt::RightToLeft</enum>
</property>
- <property name="maxLength">
- <number>8</number>
+ <property name="text">
+ <string>Regenerate</string>
</property>
</widget>
</item>
- <item row="4" column="0">
+ <item row="3" column="0">
<widget class="QCheckBox" name="custom_rtc_checkbox">
<property name="text">
<string>Custom RTC</string>
</property>
</widget>
</item>
- <item row="4" column="1">
+ <item row="3" column="1">
<widget class="QDateTimeEdit" name="custom_rtc_edit">
<property name="minimumDate">
<date>
@@ -297,6 +198,27 @@
</property>
</widget>
</item>
+ <item row="4" column="1">
+ <widget class="QLineEdit" name="rng_seed_edit">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <family>Lucida Console</family>
+ </font>
+ </property>
+ <property name="inputMask">
+ <string notr="true">HHHHHHHH</string>
+ </property>
+ <property name="maxLength">
+ <number>8</number>
+ </property>
+ </widget>
+ </item>
</layout>
</widget>
</item>
diff --git a/src/yuzu/configuration/configure_touchscreen_advanced.cpp b/src/yuzu/configuration/configure_touchscreen_advanced.cpp
index 9c1561e9d..8ced28c75 100644
--- a/src/yuzu/configuration/configure_touchscreen_advanced.cpp
+++ b/src/yuzu/configuration/configure_touchscreen_advanced.cpp
@@ -12,29 +12,41 @@ ConfigureTouchscreenAdvanced::ConfigureTouchscreenAdvanced(QWidget* parent)
ui->setupUi(this);
connect(ui->restore_defaults_button, &QPushButton::pressed, this,
- &ConfigureTouchscreenAdvanced::restoreDefaults);
+ &ConfigureTouchscreenAdvanced::RestoreDefaults);
- loadConfiguration();
+ LoadConfiguration();
resize(0, 0);
}
ConfigureTouchscreenAdvanced::~ConfigureTouchscreenAdvanced() = default;
-void ConfigureTouchscreenAdvanced::applyConfiguration() {
+void ConfigureTouchscreenAdvanced::changeEvent(QEvent* event) {
+ if (event->type() == QEvent::LanguageChange) {
+ RetranslateUI();
+ }
+
+ QDialog::changeEvent(event);
+}
+
+void ConfigureTouchscreenAdvanced::RetranslateUI() {
+ ui->retranslateUi(this);
+}
+
+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() {
+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() {
+void ConfigureTouchscreenAdvanced::RestoreDefaults() {
ui->finger_box->setValue(0);
ui->diameter_x_box->setValue(15);
ui->diameter_y_box->setValue(15);
diff --git a/src/yuzu/configuration/configure_touchscreen_advanced.h b/src/yuzu/configuration/configure_touchscreen_advanced.h
index 3d0772c87..72061492c 100644
--- a/src/yuzu/configuration/configure_touchscreen_advanced.h
+++ b/src/yuzu/configuration/configure_touchscreen_advanced.h
@@ -18,13 +18,16 @@ public:
explicit ConfigureTouchscreenAdvanced(QWidget* parent);
~ConfigureTouchscreenAdvanced() override;
- void applyConfiguration();
+ void ApplyConfiguration();
private:
+ void changeEvent(QEvent* event) override;
+ void RetranslateUI();
+
/// Load configuration settings.
- void loadConfiguration();
+ void LoadConfiguration();
/// Restore all buttons to their default values.
- void restoreDefaults();
+ void RestoreDefaults();
std::unique_ptr<Ui::ConfigureTouchscreenAdvanced> ui;
};
diff --git a/src/yuzu/configuration/configure_web.cpp b/src/yuzu/configuration/configure_web.cpp
index 18566d028..5a70ef168 100644
--- a/src/yuzu/configuration/configure_web.cpp
+++ b/src/yuzu/configuration/configure_web.cpp
@@ -22,41 +22,61 @@ ConfigureWeb::ConfigureWeb(QWidget* parent)
#ifndef USE_DISCORD_PRESENCE
ui->discord_group->setVisible(false);
#endif
- this->setConfiguration();
+
+ SetConfiguration();
+ RetranslateUI();
}
ConfigureWeb::~ConfigureWeb() = default;
-void ConfigureWeb::setConfiguration() {
- ui->web_credentials_disclaimer->setWordWrap(true);
- ui->telemetry_learn_more->setOpenExternalLinks(true);
+void ConfigureWeb::changeEvent(QEvent* event) {
+ if (event->type() == QEvent::LanguageChange) {
+ RetranslateUI();
+ }
+
+ QWidget::changeEvent(event);
+}
+
+void ConfigureWeb::RetranslateUI() {
+ ui->retranslateUi(this);
+
ui->telemetry_learn_more->setText(
tr("<a href='https://yuzu-emu.org/help/feature/telemetry/'><span style=\"text-decoration: "
"underline; color:#039be5;\">Learn more</span></a>"));
- ui->web_signup_link->setOpenExternalLinks(true);
ui->web_signup_link->setText(
tr("<a href='https://profile.yuzu-emu.org/'><span style=\"text-decoration: underline; "
"color:#039be5;\">Sign up</span></a>"));
- ui->web_token_info_link->setOpenExternalLinks(true);
+
ui->web_token_info_link->setText(
tr("<a href='https://yuzu-emu.org/wiki/yuzu-web-service/'><span style=\"text-decoration: "
"underline; color:#039be5;\">What is my token?</span></a>"));
+ ui->label_telemetry_id->setText(
+ tr("Telemetry ID: 0x%1").arg(QString::number(Core::GetTelemetryId(), 16).toUpper()));
+}
+
+void ConfigureWeb::SetConfiguration() {
+ ui->web_credentials_disclaimer->setWordWrap(true);
+
+ ui->telemetry_learn_more->setOpenExternalLinks(true);
+ ui->web_signup_link->setOpenExternalLinks(true);
+ ui->web_token_info_link->setOpenExternalLinks(true);
+
ui->toggle_telemetry->setChecked(Settings::values.enable_telemetry);
ui->edit_username->setText(QString::fromStdString(Settings::values.yuzu_username));
ui->edit_token->setText(QString::fromStdString(Settings::values.yuzu_token));
+
// Connect after setting the values, to avoid calling OnLoginChanged now
connect(ui->edit_token, &QLineEdit::textChanged, this, &ConfigureWeb::OnLoginChanged);
connect(ui->edit_username, &QLineEdit::textChanged, this, &ConfigureWeb::OnLoginChanged);
- ui->label_telemetry_id->setText(
- tr("Telemetry ID: 0x%1").arg(QString::number(Core::GetTelemetryId(), 16).toUpper()));
+
user_verified = true;
ui->toggle_discordrpc->setChecked(UISettings::values.enable_discord_presence);
}
-void ConfigureWeb::applyConfiguration() {
+void ConfigureWeb::ApplyConfiguration() {
Settings::values.enable_telemetry = ui->toggle_telemetry->isChecked();
UISettings::values.enable_discord_presence = ui->toggle_discordrpc->isChecked();
if (user_verified) {
@@ -78,12 +98,16 @@ void ConfigureWeb::RefreshTelemetryID() {
void ConfigureWeb::OnLoginChanged() {
if (ui->edit_username->text().isEmpty() && ui->edit_token->text().isEmpty()) {
user_verified = true;
- ui->label_username_verified->setPixmap(QIcon::fromTheme("checked").pixmap(16));
- ui->label_token_verified->setPixmap(QIcon::fromTheme("checked").pixmap(16));
+
+ const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("checked")).pixmap(16);
+ ui->label_username_verified->setPixmap(pixmap);
+ ui->label_token_verified->setPixmap(pixmap);
} else {
user_verified = false;
- ui->label_username_verified->setPixmap(QIcon::fromTheme("failed").pixmap(16));
- ui->label_token_verified->setPixmap(QIcon::fromTheme("failed").pixmap(16));
+
+ const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("failed")).pixmap(16);
+ ui->label_username_verified->setPixmap(pixmap);
+ ui->label_token_verified->setPixmap(pixmap);
}
}
@@ -101,18 +125,18 @@ void ConfigureWeb::OnLoginVerified() {
ui->button_verify_login->setText(tr("Verify"));
if (verify_watcher.result()) {
user_verified = true;
- ui->label_username_verified->setPixmap(QIcon::fromTheme("checked").pixmap(16));
- ui->label_token_verified->setPixmap(QIcon::fromTheme("checked").pixmap(16));
+
+ const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("checked")).pixmap(16);
+ ui->label_username_verified->setPixmap(pixmap);
+ ui->label_token_verified->setPixmap(pixmap);
} else {
- ui->label_username_verified->setPixmap(QIcon::fromTheme("failed").pixmap(16));
- ui->label_token_verified->setPixmap(QIcon::fromTheme("failed").pixmap(16));
+ const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("failed")).pixmap(16);
+ ui->label_username_verified->setPixmap(pixmap);
+ ui->label_token_verified->setPixmap(pixmap);
+
QMessageBox::critical(
this, tr("Verification failed"),
tr("Verification failed. Check that you have entered your username and token "
"correctly, and that your internet connection is working."));
}
}
-
-void ConfigureWeb::retranslateUi() {
- ui->retranslateUi(this);
-}
diff --git a/src/yuzu/configuration/configure_web.h b/src/yuzu/configuration/configure_web.h
index 7752ae4a1..9054711ea 100644
--- a/src/yuzu/configuration/configure_web.h
+++ b/src/yuzu/configuration/configure_web.h
@@ -19,16 +19,18 @@ public:
explicit ConfigureWeb(QWidget* parent = nullptr);
~ConfigureWeb() override;
- void applyConfiguration();
- void retranslateUi();
+ void ApplyConfiguration();
private:
+ void changeEvent(QEvent* event) override;
+ void RetranslateUI();
+
void RefreshTelemetryID();
void OnLoginChanged();
void VerifyLogin();
void OnLoginVerified();
- void setConfiguration();
+ void SetConfiguration();
bool user_verified = true;
QFutureWatcher<bool> verify_watcher;
diff --git a/src/yuzu/debugger/graphics/graphics_breakpoints.cpp b/src/yuzu/debugger/graphics/graphics_breakpoints.cpp
index 67ed0ba6d..1c80082a4 100644
--- a/src/yuzu/debugger/graphics/graphics_breakpoints.cpp
+++ b/src/yuzu/debugger/graphics/graphics_breakpoints.cpp
@@ -135,7 +135,7 @@ GraphicsBreakPointsWidget::GraphicsBreakPointsWidget(
std::shared_ptr<Tegra::DebugContext> debug_context, QWidget* parent)
: QDockWidget(tr("Maxwell Breakpoints"), parent), Tegra::DebugContext::BreakPointObserver(
debug_context) {
- setObjectName("TegraBreakPointsWidget");
+ setObjectName(QStringLiteral("TegraBreakPointsWidget"));
status_text = new QLabel(tr("Emulation running"));
resume_button = new QPushButton(tr("Resume"));
diff --git a/src/yuzu/debugger/graphics/graphics_surface.cpp b/src/yuzu/debugger/graphics/graphics_surface.cpp
deleted file mode 100644
index 71683da8e..000000000
--- a/src/yuzu/debugger/graphics/graphics_surface.cpp
+++ /dev/null
@@ -1,461 +0,0 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <QBoxLayout>
-#include <QComboBox>
-#include <QDebug>
-#include <QFileDialog>
-#include <QLabel>
-#include <QMouseEvent>
-#include <QPushButton>
-#include <QScrollArea>
-#include <QSpinBox>
-#include "common/vector_math.h"
-#include "core/core.h"
-#include "core/memory.h"
-#include "video_core/engines/maxwell_3d.h"
-#include "video_core/gpu.h"
-#include "video_core/textures/decoders.h"
-#include "video_core/textures/texture.h"
-#include "yuzu/debugger/graphics/graphics_surface.h"
-#include "yuzu/util/spinbox.h"
-
-static Tegra::Texture::TextureFormat ConvertToTextureFormat(
- Tegra::RenderTargetFormat render_target_format) {
- switch (render_target_format) {
- case Tegra::RenderTargetFormat::RGBA8_UNORM:
- return Tegra::Texture::TextureFormat::A8R8G8B8;
- case Tegra::RenderTargetFormat::RGB10_A2_UNORM:
- return Tegra::Texture::TextureFormat::A2B10G10R10;
- default:
- UNIMPLEMENTED_MSG("Unimplemented RT format");
- return Tegra::Texture::TextureFormat::A8R8G8B8;
- }
-}
-
-SurfacePicture::SurfacePicture(QWidget* parent, GraphicsSurfaceWidget* surface_widget_)
- : QLabel(parent), surface_widget(surface_widget_) {}
-
-SurfacePicture::~SurfacePicture() = default;
-
-void SurfacePicture::mousePressEvent(QMouseEvent* event) {
- // Only do something while the left mouse button is held down
- if (!(event->buttons() & Qt::LeftButton))
- return;
-
- if (pixmap() == nullptr)
- return;
-
- if (surface_widget)
- surface_widget->Pick(event->x() * pixmap()->width() / width(),
- event->y() * pixmap()->height() / height());
-}
-
-void SurfacePicture::mouseMoveEvent(QMouseEvent* event) {
- // We also want to handle the event if the user moves the mouse while holding down the LMB
- mousePressEvent(event);
-}
-
-GraphicsSurfaceWidget::GraphicsSurfaceWidget(std::shared_ptr<Tegra::DebugContext> debug_context,
- QWidget* parent)
- : BreakPointObserverDock(debug_context, tr("Maxwell Surface Viewer"), parent),
- surface_source(Source::RenderTarget0) {
- setObjectName("MaxwellSurface");
-
- surface_source_list = new QComboBox;
- surface_source_list->addItem(tr("Render Target 0"));
- surface_source_list->addItem(tr("Render Target 1"));
- surface_source_list->addItem(tr("Render Target 2"));
- surface_source_list->addItem(tr("Render Target 3"));
- surface_source_list->addItem(tr("Render Target 4"));
- surface_source_list->addItem(tr("Render Target 5"));
- surface_source_list->addItem(tr("Render Target 6"));
- surface_source_list->addItem(tr("Render Target 7"));
- surface_source_list->addItem(tr("Z Buffer"));
- surface_source_list->addItem(tr("Custom"));
- surface_source_list->setCurrentIndex(static_cast<int>(surface_source));
-
- surface_address_control = new CSpinBox;
- surface_address_control->SetBase(16);
- surface_address_control->SetRange(0, 0x7FFFFFFFFFFFFFFF);
- surface_address_control->SetPrefix("0x");
-
- unsigned max_dimension = 16384; // TODO: Find actual maximum
-
- surface_width_control = new QSpinBox;
- surface_width_control->setRange(0, max_dimension);
-
- surface_height_control = new QSpinBox;
- surface_height_control->setRange(0, max_dimension);
-
- surface_picker_x_control = new QSpinBox;
- surface_picker_x_control->setRange(0, max_dimension - 1);
-
- surface_picker_y_control = new QSpinBox;
- surface_picker_y_control->setRange(0, max_dimension - 1);
-
- surface_format_control = new QComboBox;
-
- // Color formats sorted by Maxwell texture format index
- surface_format_control->addItem(tr("None"));
- surface_format_control->addItem(tr("Unknown"));
- surface_format_control->addItem(tr("Unknown"));
- surface_format_control->addItem(tr("Unknown"));
- surface_format_control->addItem(tr("Unknown"));
- surface_format_control->addItem(tr("Unknown"));
- surface_format_control->addItem(tr("Unknown"));
- surface_format_control->addItem(tr("Unknown"));
- surface_format_control->addItem(tr("A8R8G8B8"));
- surface_format_control->addItem(tr("Unknown"));
- surface_format_control->addItem(tr("Unknown"));
- surface_format_control->addItem(tr("Unknown"));
- surface_format_control->addItem(tr("Unknown"));
- surface_format_control->addItem(tr("Unknown"));
- surface_format_control->addItem(tr("Unknown"));
- surface_format_control->addItem(tr("Unknown"));
- surface_format_control->addItem(tr("Unknown"));
- surface_format_control->addItem(tr("Unknown"));
- surface_format_control->addItem(tr("Unknown"));
- surface_format_control->addItem(tr("Unknown"));
- surface_format_control->addItem(tr("Unknown"));
- surface_format_control->addItem(tr("Unknown"));
- surface_format_control->addItem(tr("Unknown"));
- surface_format_control->addItem(tr("Unknown"));
- surface_format_control->addItem(tr("Unknown"));
- surface_format_control->addItem(tr("Unknown"));
- surface_format_control->addItem(tr("Unknown"));
- surface_format_control->addItem(tr("Unknown"));
- surface_format_control->addItem(tr("Unknown"));
- surface_format_control->addItem(tr("Unknown"));
- surface_format_control->addItem(tr("Unknown"));
- surface_format_control->addItem(tr("Unknown"));
- surface_format_control->addItem(tr("Unknown"));
- surface_format_control->addItem(tr("Unknown"));
- surface_format_control->addItem(tr("Unknown"));
- surface_format_control->addItem(tr("Unknown"));
- surface_format_control->addItem(tr("DXT1"));
- surface_format_control->addItem(tr("DXT23"));
- surface_format_control->addItem(tr("DXT45"));
- surface_format_control->addItem(tr("DXN1"));
- surface_format_control->addItem(tr("DXN2"));
-
- surface_info_label = new QLabel();
- surface_info_label->setWordWrap(true);
-
- surface_picture_label = new SurfacePicture(0, this);
- surface_picture_label->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
- surface_picture_label->setAlignment(Qt::AlignLeft | Qt::AlignTop);
- surface_picture_label->setScaledContents(false);
-
- auto scroll_area = new QScrollArea();
- scroll_area->setBackgroundRole(QPalette::Dark);
- scroll_area->setWidgetResizable(false);
- scroll_area->setWidget(surface_picture_label);
-
- save_surface = new QPushButton(QIcon::fromTheme("document-save"), tr("Save"));
-
- // Connections
- connect(this, &GraphicsSurfaceWidget::Update, this, &GraphicsSurfaceWidget::OnUpdate);
- connect(surface_source_list,
- static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
- &GraphicsSurfaceWidget::OnSurfaceSourceChanged);
- connect(surface_address_control, &CSpinBox::ValueChanged, this,
- &GraphicsSurfaceWidget::OnSurfaceAddressChanged);
- connect(surface_width_control, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
- this, &GraphicsSurfaceWidget::OnSurfaceWidthChanged);
- connect(surface_height_control, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
- this, &GraphicsSurfaceWidget::OnSurfaceHeightChanged);
- connect(surface_format_control,
- static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
- &GraphicsSurfaceWidget::OnSurfaceFormatChanged);
- connect(surface_picker_x_control, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
- this, &GraphicsSurfaceWidget::OnSurfacePickerXChanged);
- connect(surface_picker_y_control, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
- this, &GraphicsSurfaceWidget::OnSurfacePickerYChanged);
- connect(save_surface, &QPushButton::clicked, this, &GraphicsSurfaceWidget::SaveSurface);
-
- auto main_widget = new QWidget;
- auto main_layout = new QVBoxLayout;
- {
- auto sub_layout = new QHBoxLayout;
- sub_layout->addWidget(new QLabel(tr("Source:")));
- sub_layout->addWidget(surface_source_list);
- main_layout->addLayout(sub_layout);
- }
- {
- auto sub_layout = new QHBoxLayout;
- sub_layout->addWidget(new QLabel(tr("GPU Address:")));
- sub_layout->addWidget(surface_address_control);
- main_layout->addLayout(sub_layout);
- }
- {
- auto sub_layout = new QHBoxLayout;
- sub_layout->addWidget(new QLabel(tr("Width:")));
- sub_layout->addWidget(surface_width_control);
- main_layout->addLayout(sub_layout);
- }
- {
- auto sub_layout = new QHBoxLayout;
- sub_layout->addWidget(new QLabel(tr("Height:")));
- sub_layout->addWidget(surface_height_control);
- main_layout->addLayout(sub_layout);
- }
- {
- auto sub_layout = new QHBoxLayout;
- sub_layout->addWidget(new QLabel(tr("Format:")));
- sub_layout->addWidget(surface_format_control);
- main_layout->addLayout(sub_layout);
- }
- main_layout->addWidget(scroll_area);
-
- auto info_layout = new QHBoxLayout;
- {
- auto xy_layout = new QVBoxLayout;
- {
- {
- auto sub_layout = new QHBoxLayout;
- sub_layout->addWidget(new QLabel(tr("X:")));
- sub_layout->addWidget(surface_picker_x_control);
- xy_layout->addLayout(sub_layout);
- }
- {
- auto sub_layout = new QHBoxLayout;
- sub_layout->addWidget(new QLabel(tr("Y:")));
- sub_layout->addWidget(surface_picker_y_control);
- xy_layout->addLayout(sub_layout);
- }
- }
- info_layout->addLayout(xy_layout);
- surface_info_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
- info_layout->addWidget(surface_info_label);
- }
- main_layout->addLayout(info_layout);
-
- main_layout->addWidget(save_surface);
- main_widget->setLayout(main_layout);
- setWidget(main_widget);
-
- // Load current data - TODO: Make sure this works when emulation is not running
- if (debug_context && debug_context->at_breakpoint) {
- emit Update();
- widget()->setEnabled(debug_context->at_breakpoint);
- } else {
- widget()->setEnabled(false);
- }
-}
-
-void GraphicsSurfaceWidget::OnBreakPointHit(Tegra::DebugContext::Event event, void* data) {
- emit Update();
- widget()->setEnabled(true);
-}
-
-void GraphicsSurfaceWidget::OnResumed() {
- widget()->setEnabled(false);
-}
-
-void GraphicsSurfaceWidget::OnSurfaceSourceChanged(int new_value) {
- surface_source = static_cast<Source>(new_value);
- emit Update();
-}
-
-void GraphicsSurfaceWidget::OnSurfaceAddressChanged(qint64 new_value) {
- if (surface_address != new_value) {
- surface_address = static_cast<Tegra::GPUVAddr>(new_value);
-
- surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
- emit Update();
- }
-}
-
-void GraphicsSurfaceWidget::OnSurfaceWidthChanged(int new_value) {
- if (surface_width != static_cast<unsigned>(new_value)) {
- surface_width = static_cast<unsigned>(new_value);
-
- surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
- emit Update();
- }
-}
-
-void GraphicsSurfaceWidget::OnSurfaceHeightChanged(int new_value) {
- if (surface_height != static_cast<unsigned>(new_value)) {
- surface_height = static_cast<unsigned>(new_value);
-
- surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
- emit Update();
- }
-}
-
-void GraphicsSurfaceWidget::OnSurfaceFormatChanged(int new_value) {
- if (surface_format != static_cast<Tegra::Texture::TextureFormat>(new_value)) {
- surface_format = static_cast<Tegra::Texture::TextureFormat>(new_value);
-
- surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
- emit Update();
- }
-}
-
-void GraphicsSurfaceWidget::OnSurfacePickerXChanged(int new_value) {
- if (surface_picker_x != new_value) {
- surface_picker_x = new_value;
- Pick(surface_picker_x, surface_picker_y);
- }
-}
-
-void GraphicsSurfaceWidget::OnSurfacePickerYChanged(int new_value) {
- if (surface_picker_y != new_value) {
- surface_picker_y = new_value;
- Pick(surface_picker_x, surface_picker_y);
- }
-}
-
-void GraphicsSurfaceWidget::Pick(int x, int y) {
- surface_picker_x_control->setValue(x);
- surface_picker_y_control->setValue(y);
-
- if (x < 0 || x >= static_cast<int>(surface_width) || y < 0 ||
- y >= static_cast<int>(surface_height)) {
- surface_info_label->setText(tr("Pixel out of bounds"));
- surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
- return;
- }
-
- surface_info_label->setText(QString("Raw: <Unimplemented>\n(%1)").arg("<Unimplemented>"));
- surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
-}
-
-void GraphicsSurfaceWidget::OnUpdate() {
- auto& gpu = Core::System::GetInstance().GPU();
-
- QPixmap pixmap;
-
- switch (surface_source) {
- case Source::RenderTarget0:
- case Source::RenderTarget1:
- case Source::RenderTarget2:
- case Source::RenderTarget3:
- case Source::RenderTarget4:
- case Source::RenderTarget5:
- case Source::RenderTarget6:
- case Source::RenderTarget7: {
- // TODO: Store a reference to the registers in the debug context instead of accessing them
- // directly...
-
- const auto& registers = gpu.Maxwell3D().regs;
- const auto& rt = registers.rt[static_cast<std::size_t>(surface_source) -
- static_cast<std::size_t>(Source::RenderTarget0)];
-
- surface_address = rt.Address();
- surface_width = rt.width;
- surface_height = rt.height;
- if (rt.format != Tegra::RenderTargetFormat::NONE) {
- surface_format = ConvertToTextureFormat(rt.format);
- }
-
- break;
- }
-
- case Source::Custom: {
- // Keep user-specified values
- break;
- }
-
- default:
- qDebug() << "Unknown surface source " << static_cast<int>(surface_source);
- break;
- }
-
- surface_address_control->SetValue(surface_address);
- surface_width_control->setValue(surface_width);
- surface_height_control->setValue(surface_height);
- surface_format_control->setCurrentIndex(static_cast<int>(surface_format));
-
- if (surface_address == 0) {
- surface_picture_label->hide();
- surface_info_label->setText(tr("(invalid surface address)"));
- surface_info_label->setAlignment(Qt::AlignCenter);
- surface_picker_x_control->setEnabled(false);
- surface_picker_y_control->setEnabled(false);
- save_surface->setEnabled(false);
- return;
- }
-
- // TODO: Implement a good way to visualize alpha components!
-
- QImage decoded_image(surface_width, surface_height, QImage::Format_ARGB32);
- std::optional<VAddr> address = gpu.MemoryManager().GpuToCpuAddress(surface_address);
-
- // TODO(bunnei): Will not work with BCn formats that swizzle 4x4 tiles.
- // Needs to be fixed if we plan to use this feature more, otherwise we may remove it.
- auto unswizzled_data = Tegra::Texture::UnswizzleTexture(
- *address, 1, 1, Tegra::Texture::BytesPerPixel(surface_format), surface_width,
- surface_height, 1U);
-
- auto texture_data = Tegra::Texture::DecodeTexture(unswizzled_data, surface_format,
- surface_width, surface_height);
-
- surface_picture_label->show();
-
- for (unsigned int y = 0; y < surface_height; ++y) {
- for (unsigned int x = 0; x < surface_width; ++x) {
- Common::Vec4<u8> color;
- color[0] = texture_data[x + y * surface_width + 0];
- color[1] = texture_data[x + y * surface_width + 1];
- color[2] = texture_data[x + y * surface_width + 2];
- color[3] = texture_data[x + y * surface_width + 3];
- decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), color.a()));
- }
- }
-
- pixmap = QPixmap::fromImage(decoded_image);
- surface_picture_label->setPixmap(pixmap);
- surface_picture_label->resize(pixmap.size());
-
- // Update the info with pixel data
- surface_picker_x_control->setEnabled(true);
- surface_picker_y_control->setEnabled(true);
- Pick(surface_picker_x, surface_picker_y);
-
- // Enable saving the converted pixmap to file
- save_surface->setEnabled(true);
-}
-
-void GraphicsSurfaceWidget::SaveSurface() {
- QString png_filter = tr("Portable Network Graphic (*.png)");
- QString bin_filter = tr("Binary data (*.bin)");
-
- QString selectedFilter;
- QString filename = QFileDialog::getSaveFileName(
- this, tr("Save Surface"),
- QString("texture-0x%1.png").arg(QString::number(surface_address, 16)),
- QString("%1;;%2").arg(png_filter, bin_filter), &selectedFilter);
-
- if (filename.isEmpty()) {
- // If the user canceled the dialog, don't save anything.
- return;
- }
-
- if (selectedFilter == png_filter) {
- const QPixmap* pixmap = surface_picture_label->pixmap();
- ASSERT_MSG(pixmap != nullptr, "No pixmap set");
-
- QFile file(filename);
- file.open(QIODevice::WriteOnly);
- if (pixmap)
- pixmap->save(&file, "PNG");
- } else if (selectedFilter == bin_filter) {
- auto& gpu = Core::System::GetInstance().GPU();
- std::optional<VAddr> address = gpu.MemoryManager().GpuToCpuAddress(surface_address);
-
- const u8* buffer = Memory::GetPointer(*address);
- ASSERT_MSG(buffer != nullptr, "Memory not accessible");
-
- QFile file(filename);
- file.open(QIODevice::WriteOnly);
- int size = surface_width * surface_height * Tegra::Texture::BytesPerPixel(surface_format);
- QByteArray data(reinterpret_cast<const char*>(buffer), size);
- file.write(data);
- } else {
- UNREACHABLE_MSG("Unhandled filter selected");
- }
-}
diff --git a/src/yuzu/debugger/graphics/graphics_surface.h b/src/yuzu/debugger/graphics/graphics_surface.h
deleted file mode 100644
index 323e39d94..000000000
--- a/src/yuzu/debugger/graphics/graphics_surface.h
+++ /dev/null
@@ -1,96 +0,0 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <QLabel>
-#include <QPushButton>
-#include "video_core/memory_manager.h"
-#include "video_core/textures/texture.h"
-#include "yuzu/debugger/graphics/graphics_breakpoint_observer.h"
-
-class QComboBox;
-class QSpinBox;
-class CSpinBox;
-
-class GraphicsSurfaceWidget;
-
-class SurfacePicture : public QLabel {
- Q_OBJECT
-
-public:
- explicit SurfacePicture(QWidget* parent = nullptr,
- GraphicsSurfaceWidget* surface_widget = nullptr);
- ~SurfacePicture() override;
-
-protected slots:
- void mouseMoveEvent(QMouseEvent* event) override;
- void mousePressEvent(QMouseEvent* event) override;
-
-private:
- GraphicsSurfaceWidget* surface_widget;
-};
-
-class GraphicsSurfaceWidget : public BreakPointObserverDock {
- Q_OBJECT
-
- using Event = Tegra::DebugContext::Event;
-
- enum class Source {
- RenderTarget0 = 0,
- RenderTarget1 = 1,
- RenderTarget2 = 2,
- RenderTarget3 = 3,
- RenderTarget4 = 4,
- RenderTarget5 = 5,
- RenderTarget6 = 6,
- RenderTarget7 = 7,
- ZBuffer = 8,
- Custom = 9,
- };
-
-public:
- explicit GraphicsSurfaceWidget(std::shared_ptr<Tegra::DebugContext> debug_context,
- QWidget* parent = nullptr);
- void Pick(int x, int y);
-
-public slots:
- void OnSurfaceSourceChanged(int new_value);
- void OnSurfaceAddressChanged(qint64 new_value);
- void OnSurfaceWidthChanged(int new_value);
- void OnSurfaceHeightChanged(int new_value);
- void OnSurfaceFormatChanged(int new_value);
- void OnSurfacePickerXChanged(int new_value);
- void OnSurfacePickerYChanged(int new_value);
- void OnUpdate();
-
-signals:
- void Update();
-
-private:
- void OnBreakPointHit(Tegra::DebugContext::Event event, void* data) override;
- void OnResumed() override;
-
- void SaveSurface();
-
- QComboBox* surface_source_list;
- CSpinBox* surface_address_control;
- QSpinBox* surface_width_control;
- QSpinBox* surface_height_control;
- QComboBox* surface_format_control;
-
- SurfacePicture* surface_picture_label;
- QSpinBox* surface_picker_x_control;
- QSpinBox* surface_picker_y_control;
- QLabel* surface_info_label;
- QPushButton* save_surface;
-
- Source surface_source;
- Tegra::GPUVAddr surface_address;
- unsigned surface_width;
- unsigned surface_height;
- Tegra::Texture::TextureFormat surface_format;
- int surface_picker_x = 0;
- int surface_picker_y = 0;
-};
diff --git a/src/yuzu/debugger/profiler.cpp b/src/yuzu/debugger/profiler.cpp
index 8b30e0a85..f594ef076 100644
--- a/src/yuzu/debugger/profiler.cpp
+++ b/src/yuzu/debugger/profiler.cpp
@@ -7,6 +7,7 @@
#include <QMouseEvent>
#include <QPainter>
#include <QString>
+#include <QTimer>
#include "common/common_types.h"
#include "common/microprofile.h"
#include "yuzu/debugger/profiler.h"
@@ -46,7 +47,7 @@ private:
#endif
MicroProfileDialog::MicroProfileDialog(QWidget* parent) : QWidget(parent, Qt::Dialog) {
- setObjectName("MicroProfile");
+ setObjectName(QStringLiteral("MicroProfile"));
setWindowTitle(tr("MicroProfile"));
resize(1000, 600);
// Remove the "?" button from the titlebar and enable the maximize button
@@ -190,7 +191,7 @@ void MicroProfileDrawText(int x, int y, u32 hex_color, const char* text, u32 tex
for (u32 i = 0; i < text_length; ++i) {
// Position the text baseline 1 pixel above the bottom of the text cell, this gives nice
// vertical alignment of text for a wide range of tested fonts.
- mp_painter->drawText(x, y + MICROPROFILE_TEXT_HEIGHT - 2, QChar(text[i]));
+ mp_painter->drawText(x, y + MICROPROFILE_TEXT_HEIGHT - 2, QString{QLatin1Char{text[i]}});
x += MICROPROFILE_TEXT_WIDTH + 1;
}
}
diff --git a/src/yuzu/debugger/profiler.h b/src/yuzu/debugger/profiler.h
index eae1e9e3c..8e69fdb06 100644
--- a/src/yuzu/debugger/profiler.h
+++ b/src/yuzu/debugger/profiler.h
@@ -4,10 +4,11 @@
#pragma once
-#include <QAbstractItemModel>
-#include <QDockWidget>
-#include <QTimer>
-#include "common/microprofile.h"
+#include <QWidget>
+
+class QAction;
+class QHideEvent;
+class QShowEvent;
class MicroProfileDialog : public QWidget {
Q_OBJECT
diff --git a/src/yuzu/debugger/wait_tree.cpp b/src/yuzu/debugger/wait_tree.cpp
index 06ad74ffe..cd8180f8b 100644
--- a/src/yuzu/debugger/wait_tree.cpp
+++ b/src/yuzu/debugger/wait_tree.cpp
@@ -91,19 +91,19 @@ WaitTreeMutexInfo::WaitTreeMutexInfo(VAddr mutex_address, const Kernel::HandleTa
WaitTreeMutexInfo::~WaitTreeMutexInfo() = default;
QString WaitTreeMutexInfo::GetText() const {
- return tr("waiting for mutex 0x%1").arg(mutex_address, 16, 16, QLatin1Char('0'));
+ return tr("waiting for mutex 0x%1").arg(mutex_address, 16, 16, QLatin1Char{'0'});
}
std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeMutexInfo::GetChildren() const {
- std::vector<std::unique_ptr<WaitTreeItem>> list;
-
- bool has_waiters = (mutex_value & Kernel::Mutex::MutexHasWaitersFlag) != 0;
+ const bool has_waiters = (mutex_value & Kernel::Mutex::MutexHasWaitersFlag) != 0;
+ std::vector<std::unique_ptr<WaitTreeItem>> list;
list.push_back(std::make_unique<WaitTreeText>(tr("has waiters: %1").arg(has_waiters)));
list.push_back(std::make_unique<WaitTreeText>(
- tr("owner handle: 0x%1").arg(owner_handle, 8, 16, QLatin1Char('0'))));
- if (owner != nullptr)
+ tr("owner handle: 0x%1").arg(owner_handle, 8, 16, QLatin1Char{'0'})));
+ if (owner != nullptr) {
list.push_back(std::make_unique<WaitTreeThread>(*owner));
+ }
return list;
}
@@ -121,11 +121,14 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeCallstack::GetChildren() cons
u64 base_pointer = thread.GetContext().cpu_registers[BaseRegister];
while (base_pointer != 0) {
- u64 lr = Memory::Read64(base_pointer + sizeof(u64));
- if (lr == 0)
+ const u64 lr = Memory::Read64(base_pointer + sizeof(u64));
+ if (lr == 0) {
break;
- list.push_back(
- std::make_unique<WaitTreeText>(tr("0x%1").arg(lr - sizeof(u32), 16, 16, QChar('0'))));
+ }
+
+ list.push_back(std::make_unique<WaitTreeText>(
+ tr("0x%1").arg(lr - sizeof(u32), 16, 16, QLatin1Char{'0'})));
+
base_pointer = Memory::Read64(base_pointer);
}
@@ -174,10 +177,10 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeWaitObject::GetChildren() con
QString WaitTreeWaitObject::GetResetTypeQString(Kernel::ResetType reset_type) {
switch (reset_type) {
- case Kernel::ResetType::OneShot:
- return tr("one shot");
- case Kernel::ResetType::Sticky:
- return tr("sticky");
+ case Kernel::ResetType::Automatic:
+ return tr("automatic reset");
+ case Kernel::ResetType::Manual:
+ return tr("manual reset");
}
UNREACHABLE();
return {};
@@ -227,13 +230,15 @@ QString WaitTreeThread::GetText() const {
case Kernel::ThreadStatus::WaitIPC:
status = tr("waiting for IPC reply");
break;
- case Kernel::ThreadStatus::WaitSynchAll:
- case Kernel::ThreadStatus::WaitSynchAny:
+ 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");
+ break;
case Kernel::ThreadStatus::WaitArb:
status = tr("waiting for address arbiter");
break;
@@ -247,9 +252,9 @@ QString WaitTreeThread::GetText() const {
const auto& context = thread.GetContext();
const QString pc_info = tr(" PC = 0x%1 LR = 0x%2")
- .arg(context.pc, 8, 16, QLatin1Char('0'))
- .arg(context.cpu_registers[30], 8, 16, QLatin1Char('0'));
- return WaitTreeWaitObject::GetText() + pc_info + " (" + status + ") ";
+ .arg(context.pc, 8, 16, QLatin1Char{'0'})
+ .arg(context.cpu_registers[30], 8, 16, QLatin1Char{'0'});
+ return QStringLiteral("%1%2 (%3) ").arg(WaitTreeWaitObject::GetText(), pc_info, status);
}
QColor WaitTreeThread::GetColor() const {
@@ -266,9 +271,9 @@ QColor WaitTreeThread::GetColor() const {
return QColor(Qt::GlobalColor::darkRed);
case Kernel::ThreadStatus::WaitSleep:
return QColor(Qt::GlobalColor::darkYellow);
- case Kernel::ThreadStatus::WaitSynchAll:
- case Kernel::ThreadStatus::WaitSynchAny:
+ case Kernel::ThreadStatus::WaitSynch:
case Kernel::ThreadStatus::WaitMutex:
+ case Kernel::ThreadStatus::WaitCondVar:
case Kernel::ThreadStatus::WaitArb:
return QColor(Qt::GlobalColor::red);
case Kernel::ThreadStatus::Dormant:
@@ -321,10 +326,9 @@ 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::WaitSynchAny ||
- thread.GetStatus() == Kernel::ThreadStatus::WaitSynchAll) {
+ if (thread.GetStatus() == Kernel::ThreadStatus::WaitSynch) {
list.push_back(std::make_unique<WaitTreeObjectList>(thread.GetWaitObjects(),
- thread.IsSleepingOnWaitAll()));
+ thread.IsSleepingOnWait()));
}
list.push_back(std::make_unique<WaitTreeCallstack>(thread));
@@ -423,7 +427,7 @@ void WaitTreeModel::InitItems() {
}
WaitTreeWidget::WaitTreeWidget(QWidget* parent) : QDockWidget(tr("Wait Tree"), parent) {
- setObjectName("WaitTreeWidget");
+ setObjectName(QStringLiteral("WaitTreeWidget"));
view = new QTreeView(this);
view->setHeaderHidden(true);
setWidget(view);
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index c0e3c5fa9..83d675773 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -14,10 +14,10 @@
#include <QMenu>
#include <QThreadPool>
#include <fmt/format.h>
-#include "common/common_paths.h"
#include "common/common_types.h"
#include "common/logging/log.h"
#include "core/file_sys/patch_manager.h"
+#include "core/file_sys/registered_cache.h"
#include "yuzu/compatibility_list.h"
#include "yuzu/game_list.h"
#include "yuzu/game_list_p.h"
@@ -47,7 +47,7 @@ bool GameListSearchField::KeyReleaseEater::eventFilter(QObject* obj, QEvent* eve
return QObject::eventFilter(obj, event);
} else {
gamelist->search_field->edit_filter->clear();
- edit_filter_text = "";
+ edit_filter_text.clear();
}
break;
}
@@ -70,9 +70,9 @@ bool GameListSearchField::KeyReleaseEater::eventFilter(QObject* obj, QEvent* eve
}
if (resultCount == 1) {
// To avoid loading error dialog loops while confirming them using enter
- // Also users usually want to run a diffrent game after closing one
- gamelist->search_field->edit_filter->setText("");
- edit_filter_text = "";
+ // Also users usually want to run a different game after closing one
+ gamelist->search_field->edit_filter->clear();
+ edit_filter_text.clear();
emit gamelist->GameChosen(file_path);
} else {
return QObject::eventFilter(obj, event);
@@ -92,7 +92,7 @@ void GameListSearchField::setFilterResult(int visible, int total) {
}
void GameListSearchField::clear() {
- edit_filter->setText("");
+ edit_filter->clear();
}
void GameListSearchField::setFocus() {
@@ -102,25 +102,26 @@ void GameListSearchField::setFocus() {
}
GameListSearchField::GameListSearchField(GameList* parent) : QWidget{parent} {
- KeyReleaseEater* keyReleaseEater = new KeyReleaseEater(parent);
+ auto* const key_release_eater = new KeyReleaseEater(parent);
layout_filter = new QHBoxLayout;
layout_filter->setMargin(8);
label_filter = new QLabel;
label_filter->setText(tr("Filter:"));
edit_filter = new QLineEdit;
- edit_filter->setText("");
+ edit_filter->clear();
edit_filter->setPlaceholderText(tr("Enter pattern to filter"));
- edit_filter->installEventFilter(keyReleaseEater);
+ edit_filter->installEventFilter(key_release_eater);
edit_filter->setClearButtonEnabled(true);
connect(edit_filter, &QLineEdit::textChanged, parent, &GameList::onTextChanged);
label_filter_result = new QLabel;
button_filter_close = new QToolButton(this);
- button_filter_close->setText("X");
+ button_filter_close->setText(QStringLiteral("X"));
button_filter_close->setCursor(Qt::ArrowCursor);
- button_filter_close->setStyleSheet("QToolButton{ border: none; padding: 0px; color: "
- "#000000; font-weight: bold; background: #F0F0F0; }"
- "QToolButton:hover{ border: none; padding: 0px; color: "
- "#EEEEEE; font-weight: bold; background: #E81123}");
+ button_filter_close->setStyleSheet(
+ QStringLiteral("QToolButton{ border: none; padding: 0px; color: "
+ "#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);
layout_filter->setSpacing(10);
layout_filter->addWidget(label_filter);
@@ -140,36 +141,34 @@ GameListSearchField::GameListSearchField(GameList* parent) : QWidget{parent} {
*/
static bool ContainsAllWords(const QString& haystack, const QString& userinput) {
const QStringList userinput_split =
- userinput.split(' ', QString::SplitBehavior::SkipEmptyParts);
+ userinput.split(QLatin1Char{' '}, QString::SplitBehavior::SkipEmptyParts);
return std::all_of(userinput_split.begin(), userinput_split.end(),
[&haystack](const QString& s) { return haystack.contains(s); });
}
// Event in order to filter the gamelist after editing the searchfield
-void GameList::onTextChanged(const QString& newText) {
- int rowCount = tree_view->model()->rowCount();
- QString edit_filter_text = newText.toLower();
-
- QModelIndex root_index = item_model->invisibleRootItem()->index();
+void GameList::onTextChanged(const QString& new_text) {
+ const int row_count = tree_view->model()->rowCount();
+ const QString edit_filter_text = new_text.toLower();
+ const QModelIndex root_index = item_model->invisibleRootItem()->index();
// If the searchfield is empty every item is visible
// Otherwise the filter gets applied
if (edit_filter_text.isEmpty()) {
- for (int i = 0; i < rowCount; ++i) {
+ for (int i = 0; i < row_count; ++i) {
tree_view->setRowHidden(i, root_index, false);
}
- search_field->setFilterResult(rowCount, rowCount);
+ search_field->setFilterResult(row_count, row_count);
} else {
int result_count = 0;
- for (int i = 0; i < rowCount; ++i) {
+ for (int i = 0; i < row_count; ++i) {
const QStandardItem* child_file = item_model->item(i, 0);
const QString file_path =
child_file->data(GameListItemPath::FullPathRole).toString().toLower();
- QString file_name = file_path.mid(file_path.lastIndexOf('/') + 1);
const QString file_title =
child_file->data(GameListItemPath::TitleRole).toString().toLower();
- const QString file_programmid =
+ const QString file_program_id =
child_file->data(GameListItemPath::ProgramIdRole).toString().toLower();
// Only items which filename in combination with its title contains all words
@@ -177,14 +176,16 @@ void GameList::onTextChanged(const QString& newText) {
// The search is case insensitive because of toLower()
// I decided not to use Qt::CaseInsensitive in containsAllWords to prevent
// multiple conversions of edit_filter_text for each game in the gamelist
- if (ContainsAllWords(file_name.append(' ').append(file_title), edit_filter_text) ||
- (file_programmid.count() == 16 && edit_filter_text.contains(file_programmid))) {
+ const QString file_name = file_path.mid(file_path.lastIndexOf(QLatin1Char{'/'}) + 1) +
+ QLatin1Char{' '} + file_title;
+ if (ContainsAllWords(file_name, edit_filter_text) ||
+ (file_program_id.count() == 16 && edit_filter_text.contains(file_program_id))) {
tree_view->setRowHidden(i, root_index, false);
++result_count;
} else {
tree_view->setRowHidden(i, root_index, true);
}
- search_field->setFilterResult(result_count, rowCount);
+ search_field->setFilterResult(result_count, row_count);
}
}
}
@@ -193,8 +194,9 @@ void GameList::onFilterCloseClicked() {
main_window->filterBarSetChecked(false);
}
-GameList::GameList(FileSys::VirtualFilesystem vfs, GMainWindow* parent)
- : QWidget{parent}, vfs(std::move(vfs)) {
+GameList::GameList(FileSys::VirtualFilesystem vfs, FileSys::ManualContentProvider* provider,
+ GMainWindow* parent)
+ : QWidget{parent}, vfs(std::move(vfs)), provider(provider) {
watcher = new QFileSystemWatcher(this);
connect(watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory);
@@ -214,7 +216,7 @@ GameList::GameList(FileSys::VirtualFilesystem vfs, GMainWindow* parent)
tree_view->setEditTriggers(QHeaderView::NoEditTriggers);
tree_view->setUniformRowHeights(true);
tree_view->setContextMenuPolicy(Qt::CustomContextMenu);
- tree_view->setStyleSheet("QTreeView{ border: none; }");
+ tree_view->setStyleSheet(QStringLiteral("QTreeView{ border: none; }"));
item_model->insertColumns(0, UISettings::values.show_add_ons ? COLUMN_COUNT : COLUMN_COUNT - 1);
item_model->setHeaderData(COLUMN_NAME, Qt::Horizontal, tr("Name"));
@@ -280,9 +282,9 @@ void GameList::ValidateEntry(const QModelIndex& item) {
const QFileInfo file_info{file_path};
if (file_info.isDir()) {
const QDir dir{file_path};
- const QStringList matching_main = dir.entryList(QStringList("main"), QDir::Files);
+ const QStringList matching_main = dir.entryList({QStringLiteral("main")}, QDir::Files);
if (matching_main.size() == 1) {
- emit GameChosen(dir.path() + DIR_SEP + matching_main[0]);
+ emit GameChosen(dir.path() + QDir::separator() + matching_main[0]);
}
return;
}
@@ -329,6 +331,8 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
QMenu context_menu;
QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location"));
QAction* open_lfs_location = context_menu.addAction(tr("Open Mod Data Location"));
+ QAction* open_transferable_shader_cache =
+ context_menu.addAction(tr("Open Transferable Shader Cache"));
context_menu.addSeparator();
QAction* dump_romfs = context_menu.addAction(tr("Dump RomFS"));
QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard"));
@@ -344,6 +348,8 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
[&]() { emit OpenFolderRequested(program_id, GameListOpenTarget::SaveData); });
connect(open_lfs_location, &QAction::triggered,
[&]() { emit OpenFolderRequested(program_id, GameListOpenTarget::ModData); });
+ connect(open_transferable_shader_cache, &QAction::triggered,
+ [&]() { emit OpenTransferableShaderCacheRequested(program_id); });
connect(dump_romfs, &QAction::triggered, [&]() { emit DumpRomFSRequested(program_id, path); });
connect(copy_tid, &QAction::triggered, [&]() { emit CopyTIDRequested(program_id); });
connect(navigate_to_gamedb_entry, &QAction::triggered,
@@ -354,7 +360,7 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
}
void GameList::LoadCompatibilityList() {
- QFile compat_list{":compatibility_list/compatibility_list.json"};
+ QFile compat_list{QStringLiteral(":compatibility_list/compatibility_list.json")};
if (!compat_list.open(QFile::ReadOnly | QFile::Text)) {
LOG_ERROR(Frontend, "Unable to open game compatibility list");
@@ -372,25 +378,27 @@ void GameList::LoadCompatibilityList() {
return;
}
- const QString string_content = content;
- QJsonDocument json = QJsonDocument::fromJson(string_content.toUtf8());
- QJsonArray arr = json.array();
+ const QJsonDocument json = QJsonDocument::fromJson(content);
+ const QJsonArray arr = json.array();
- for (const QJsonValueRef value : arr) {
- QJsonObject game = value.toObject();
+ for (const QJsonValue value : arr) {
+ const QJsonObject game = value.toObject();
+ const QString compatibility_key = QStringLiteral("compatibility");
- if (game.contains("compatibility") && game["compatibility"].isDouble()) {
- int compatibility = game["compatibility"].toInt();
- QString directory = game["directory"].toString();
- QJsonArray ids = game["releases"].toArray();
+ if (!game.contains(compatibility_key) || !game[compatibility_key].isDouble()) {
+ continue;
+ }
- for (const QJsonValueRef id_ref : ids) {
- QJsonObject id_object = id_ref.toObject();
- QString id = id_object["id"].toString();
- compatibility_list.emplace(
- id.toUpper().toStdString(),
- std::make_pair(QString::number(compatibility), directory));
- }
+ const int compatibility = game[compatibility_key].toInt();
+ const QString directory = game[QStringLiteral("directory")].toString();
+ const QJsonArray ids = game[QStringLiteral("releases")].toArray();
+
+ for (const QJsonValue id_ref : ids) {
+ const QJsonObject id_object = id_ref.toObject();
+ const QString id = id_object[QStringLiteral("id")].toString();
+
+ compatibility_list.emplace(id.toUpper().toStdString(),
+ std::make_pair(QString::number(compatibility), directory));
}
}
}
@@ -428,7 +436,8 @@ void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) {
emit ShouldCancelWorker();
- GameListWorker* worker = new GameListWorker(vfs, dir_path, deep_scan, compatibility_list);
+ GameListWorker* worker =
+ new GameListWorker(vfs, provider, dir_path, deep_scan, compatibility_list);
connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection);
connect(worker, &GameListWorker::Finished, this, &GameList::DonePopulating,
@@ -457,12 +466,16 @@ void GameList::LoadInterfaceLayout() {
item_model->sort(header->sortIndicatorSection(), header->sortIndicatorOrder());
}
-const QStringList GameList::supported_file_extensions = {"nso", "nro", "nca", "xci", "nsp"};
+const QStringList GameList::supported_file_extensions = {
+ QStringLiteral("nso"), QStringLiteral("nro"), QStringLiteral("nca"),
+ QStringLiteral("xci"), QStringLiteral("nsp"),
+};
void GameList::RefreshGameDirectory() {
- if (!UISettings::values.gamedir.isEmpty() && current_worker != nullptr) {
+ if (!UISettings::values.game_directory_path.isEmpty() && current_worker != nullptr) {
LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list.");
search_field->clear();
- PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
+ PopulateAsync(UISettings::values.game_directory_path,
+ UISettings::values.game_directory_deepscan);
}
}
diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h
index b317eb2fc..f8f8bd6c5 100644
--- a/src/yuzu/game_list.h
+++ b/src/yuzu/game_list.h
@@ -26,8 +26,9 @@ class GameListSearchField;
class GMainWindow;
namespace FileSys {
+class ManualContentProvider;
class VfsFilesystem;
-}
+} // namespace FileSys
enum class GameListOpenTarget {
SaveData,
@@ -47,7 +48,8 @@ public:
COLUMN_COUNT, // Number of columns
};
- explicit GameList(std::shared_ptr<FileSys::VfsFilesystem> vfs, GMainWindow* parent = nullptr);
+ explicit GameList(std::shared_ptr<FileSys::VfsFilesystem> vfs,
+ FileSys::ManualContentProvider* provider, GMainWindow* parent = nullptr);
~GameList() override;
void clearFilter();
@@ -66,6 +68,7 @@ signals:
void GameChosen(QString game_path);
void ShouldCancelWorker();
void OpenFolderRequested(u64 program_id, GameListOpenTarget target);
+ void OpenTransferableShaderCacheRequested(u64 program_id);
void DumpRomFSRequested(u64 program_id, const std::string& game_path);
void CopyTIDRequested(u64 program_id);
void NavigateToGamedbEntryRequested(u64 program_id,
@@ -73,7 +76,7 @@ signals:
void OpenPerGameGeneralRequested(const std::string& file);
private slots:
- void onTextChanged(const QString& newText);
+ void onTextChanged(const QString& new_text);
void onFilterCloseClicked();
private:
@@ -85,6 +88,7 @@ private:
void RefreshGameDirectory();
std::shared_ptr<FileSys::VfsFilesystem> vfs;
+ FileSys::ManualContentProvider* provider;
GameListSearchField* search_field;
GMainWindow* main_window = nullptr;
QVBoxLayout* layout = nullptr;
diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h
index 3db0e90da..0b458ef48 100644
--- a/src/yuzu/game_list_p.h
+++ b/src/yuzu/game_list_p.h
@@ -4,11 +4,9 @@
#pragma once
-#include <algorithm>
#include <array>
#include <map>
#include <string>
-#include <unordered_map>
#include <utility>
#include <QCoreApplication>
@@ -25,8 +23,8 @@
#include "yuzu/util/util.h"
/**
- * Gets the default icon (for games without valid SMDH)
- * @param large If true, returns large icon (48x48), otherwise returns small icon (24x24)
+ * Gets the default icon (for games without valid title metadata)
+ * @param size The desired width and height of the default icon.
* @return QPixmap default icon
*/
static QPixmap GetDefaultIcon(u32 size) {
@@ -46,7 +44,7 @@ public:
* A specialization of GameListItem for path values.
* This class ensures that for every full path value it holds, a correct string representation
* of just the filename (with no extension) will be displayed to the user.
- * If this class receives valid SMDH data, it will also display game icons and titles.
+ * If this class receives valid title metadata, it will also display game icons and titles.
*/
class GameListItemPath : public GameListItem {
public:
@@ -95,7 +93,7 @@ public:
if (row2.isEmpty())
return row1;
- return row1 + "\n " + row2;
+ return QString(row1 + QStringLiteral("\n ") + row2);
}
return GameListItem::data(role);
@@ -115,13 +113,14 @@ public:
};
// clang-format off
static const std::map<QString, CompatStatus> status_data = {
- {"0", {"#5c93ed", QT_TR_NOOP("Perfect"), QT_TR_NOOP("Game functions flawless with no audio or graphical glitches, all tested functionality works as intended without\nany workarounds needed.")}},
- {"1", {"#47d35c", QT_TR_NOOP("Great"), QT_TR_NOOP("Game functions with minor graphical or audio glitches and is playable from start to finish. May require some\nworkarounds.")}},
- {"2", {"#94b242", QT_TR_NOOP("Okay"), QT_TR_NOOP("Game functions with major graphical or audio glitches, but game is playable from start to finish with\nworkarounds.")}},
- {"3", {"#f2d624", QT_TR_NOOP("Bad"), QT_TR_NOOP("Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches\neven with workarounds.")}},
- {"4", {"#FF0000", QT_TR_NOOP("Intro/Menu"), QT_TR_NOOP("Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start\nScreen.")}},
- {"5", {"#828282", QT_TR_NOOP("Won't Boot"), QT_TR_NOOP("The game crashes when attempting to startup.")}},
- {"99", {"#000000", QT_TR_NOOP("Not Tested"), QT_TR_NOOP("The game has not yet been tested.")}}};
+ {QStringLiteral("0"), {QStringLiteral("#5c93ed"), QT_TR_NOOP("Perfect"), QT_TR_NOOP("Game functions flawless with no audio or graphical glitches, all tested functionality works as intended without\nany workarounds needed.")}},
+ {QStringLiteral("1"), {QStringLiteral("#47d35c"), QT_TR_NOOP("Great"), QT_TR_NOOP("Game functions with minor graphical or audio glitches and is playable from start to finish. May require some\nworkarounds.")}},
+ {QStringLiteral("2"), {QStringLiteral("#94b242"), QT_TR_NOOP("Okay"), QT_TR_NOOP("Game functions with major graphical or audio glitches, but game is playable from start to finish with\nworkarounds.")}},
+ {QStringLiteral("3"), {QStringLiteral("#f2d624"), QT_TR_NOOP("Bad"), QT_TR_NOOP("Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches\neven with workarounds.")}},
+ {QStringLiteral("4"), {QStringLiteral("#FF0000"), QT_TR_NOOP("Intro/Menu"), QT_TR_NOOP("Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start\nScreen.")}},
+ {QStringLiteral("5"), {QStringLiteral("#828282"), QT_TR_NOOP("Won't Boot"), QT_TR_NOOP("The game crashes when attempting to startup.")}},
+ {QStringLiteral("99"), {QStringLiteral("#000000"), QT_TR_NOOP("Not Tested"), QT_TR_NOOP("The game has not yet been tested.")}},
+ };
// clang-format on
auto iterator = status_data.find(compatibility);
diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp
index b37710f59..4f30e9147 100644
--- a/src/yuzu/game_list_worker.cpp
+++ b/src/yuzu/game_list_worker.cpp
@@ -8,16 +8,21 @@
#include <vector>
#include <QDir>
+#include <QFile>
#include <QFileInfo>
+#include <QSettings>
#include "common/common_paths.h"
#include "common/file_util.h"
+#include "core/core.h"
+#include "core/file_sys/card_image.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/control_metadata.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/submission_package.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/loader/loader.h"
#include "yuzu/compatibility_list.h"
@@ -27,13 +32,108 @@
#include "yuzu/ui_settings.h"
namespace {
+
+QString GetGameListCachedObject(const std::string& filename, const std::string& ext,
+ const std::function<QString()>& generator) {
+ if (!UISettings::values.cache_game_list || filename == "0000000000000000") {
+ return generator();
+ }
+
+ const auto path = FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + "game_list" +
+ DIR_SEP + filename + '.' + ext;
+
+ FileUtil::CreateFullPath(path);
+
+ if (!FileUtil::Exists(path)) {
+ const auto str = generator();
+
+ QFile file{QString::fromStdString(path)};
+ if (file.open(QFile::WriteOnly)) {
+ file.write(str.toUtf8());
+ }
+
+ return str;
+ }
+
+ QFile file{QString::fromStdString(path)};
+ if (file.open(QFile::ReadOnly)) {
+ return QString::fromUtf8(file.readAll());
+ }
+
+ return generator();
+}
+
+std::pair<std::vector<u8>, std::string> GetGameListCachedObject(
+ const std::string& filename, const std::string& ext,
+ const std::function<std::pair<std::vector<u8>, std::string>()>& generator) {
+ if (!UISettings::values.cache_game_list || filename == "0000000000000000") {
+ return generator();
+ }
+
+ const auto path1 = FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + "game_list" +
+ DIR_SEP + filename + ".jpeg";
+ const auto path2 = FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + "game_list" +
+ DIR_SEP + filename + ".appname.txt";
+
+ FileUtil::CreateFullPath(path1);
+
+ if (!FileUtil::Exists(path1) || !FileUtil::Exists(path2)) {
+ const auto [icon, nacp] = generator();
+
+ QFile file1{QString::fromStdString(path1)};
+ if (!file1.open(QFile::WriteOnly)) {
+ LOG_ERROR(Frontend, "Failed to open cache file.");
+ return generator();
+ }
+
+ if (!file1.resize(icon.size())) {
+ LOG_ERROR(Frontend, "Failed to resize cache file to necessary size.");
+ return generator();
+ }
+
+ if (file1.write(reinterpret_cast<const char*>(icon.data()), icon.size()) != icon.size()) {
+ LOG_ERROR(Frontend, "Failed to write data to cache file.");
+ return generator();
+ }
+
+ QFile file2{QString::fromStdString(path2)};
+ if (file2.open(QFile::WriteOnly)) {
+ file2.write(nacp.data(), nacp.size());
+ }
+
+ return std::make_pair(icon, nacp);
+ }
+
+ QFile file1(QString::fromStdString(path1));
+ QFile file2(QString::fromStdString(path2));
+
+ if (!file1.open(QFile::ReadOnly)) {
+ LOG_ERROR(Frontend, "Failed to open cache file for reading.");
+ return generator();
+ }
+
+ if (!file2.open(QFile::ReadOnly)) {
+ LOG_ERROR(Frontend, "Failed to open cache file for reading.");
+ return generator();
+ }
+
+ std::vector<u8> vec(file1.size());
+ if (file1.read(reinterpret_cast<char*>(vec.data()), vec.size()) !=
+ static_cast<s64>(vec.size())) {
+ return generator();
+ }
+
+ const auto data = file2.readAll();
+ return std::make_pair(vec, data.toStdString());
+}
+
void GetMetadataFromControlNCA(const FileSys::PatchManager& patch_manager, const FileSys::NCA& nca,
std::vector<u8>& icon, std::string& name) {
- auto [nacp, icon_file] = patch_manager.ParseControlNCA(nca);
- if (icon_file != nullptr)
- icon = icon_file->ReadAllBytes();
- if (nacp != nullptr)
- name = nacp->GetApplicationName();
+ std::tie(icon, name) = GetGameListCachedObject(
+ fmt::format("{:016X}", patch_manager.GetTitleID()), {}, [&patch_manager, &nca] {
+ const auto [nacp, icon_f] = patch_manager.ParseControlNCA(nca);
+ return std::make_pair(icon_f->ReadAllBytes(), nacp->GetApplicationName());
+ });
}
bool HasSupportedFileExtension(const std::string& file_name) {
@@ -42,7 +142,7 @@ bool HasSupportedFileExtension(const std::string& file_name) {
}
bool IsExtractedNCAMain(const std::string& file_name) {
- return QFileInfo(QString::fromStdString(file_name)).fileName() == "main";
+ return QFileInfo(QString::fromStdString(file_name)).fileName() == QStringLiteral("main");
}
QString FormatGameName(const std::string& physical_name) {
@@ -94,7 +194,7 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri
const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
// The game list uses this as compatibility number for untested games
- QString compatibility{"99"};
+ QString compatibility{QStringLiteral("99")};
if (it != compatibility_list.end()) {
compatibility = it->second.first;
}
@@ -111,28 +211,36 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri
};
if (UISettings::values.show_add_ons) {
- list.insert(
- 2, new GameListItem(FormatPatchNameVersions(patch, loader, loader.IsRomFSUpdatable())));
+ const auto patch_versions = GetGameListCachedObject(
+ fmt::format("{:016X}", patch.GetTitleID()), "pv.txt", [&patch, &loader] {
+ return FormatPatchNameVersions(patch, loader, loader.IsRomFSUpdatable());
+ });
+ list.insert(2, new GameListItem(patch_versions));
}
return list;
}
} // Anonymous namespace
-GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs, QString dir_path, bool deep_scan,
- const CompatibilityList& compatibility_list)
- : vfs(std::move(vfs)), dir_path(std::move(dir_path)), deep_scan(deep_scan),
+GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs,
+ FileSys::ManualContentProvider* provider, QString dir_path,
+ bool deep_scan, const CompatibilityList& compatibility_list)
+ : vfs(std::move(vfs)), provider(provider), dir_path(std::move(dir_path)), deep_scan(deep_scan),
compatibility_list(compatibility_list) {}
GameListWorker::~GameListWorker() = default;
-void GameListWorker::AddInstalledTitlesToGameList() {
- const auto cache = Service::FileSystem::GetUnionContents();
- const auto installed_games = cache.ListEntriesFilter(FileSys::TitleType::Application,
- FileSys::ContentRecordType::Program);
+void GameListWorker::AddTitlesToGameList() {
+ const auto& cache = dynamic_cast<FileSys::ContentProviderUnion&>(
+ Core::System::GetInstance().GetContentProvider());
+ const auto installed_games = cache.ListEntriesFilterOrigin(
+ std::nullopt, FileSys::TitleType::Application, FileSys::ContentRecordType::Program);
- for (const auto& game : installed_games) {
- const auto file = cache.GetEntryUnparsed(game);
+ for (const auto& [slot, game] : installed_games) {
+ if (slot == FileSys::ContentProviderUnionSlot::FrontendManual)
+ continue;
+
+ const auto file = cache.GetEntryUnparsed(game.title_id, game.type);
std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(file);
if (!loader)
continue;
@@ -150,45 +258,13 @@ void GameListWorker::AddInstalledTitlesToGameList() {
emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, icon, *loader, program_id,
compatibility_list, patch));
}
-
- const auto control_data = cache.ListEntriesFilter(FileSys::TitleType::Application,
- FileSys::ContentRecordType::Control);
-
- for (const auto& entry : control_data) {
- auto nca = cache.GetEntry(entry);
- if (nca != nullptr) {
- nca_control_map.insert_or_assign(entry.title_id, std::move(nca));
- }
- }
-}
-
-void GameListWorker::FillControlMap(const std::string& dir_path) {
- const auto nca_control_callback = [this](u64* num_entries_out, const std::string& directory,
- const std::string& virtual_name) -> bool {
- if (stop_processing) {
- // Breaks the callback loop
- return false;
- }
-
- const std::string physical_name = directory + DIR_SEP + virtual_name;
- const QFileInfo file_info(QString::fromStdString(physical_name));
- if (!file_info.isDir() && file_info.suffix() == QStringLiteral("nca")) {
- auto nca =
- std::make_unique<FileSys::NCA>(vfs->OpenFile(physical_name, FileSys::Mode::Read));
- if (nca->GetType() == FileSys::NCAContentType::Control) {
- const u64 title_id = nca->GetTitleId();
- nca_control_map.insert_or_assign(title_id, std::move(nca));
- }
- }
- return true;
- };
-
- FileUtil::ForeachDirectoryEntry(nullptr, dir_path, nca_control_callback);
}
-void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) {
- const auto callback = [this, recursion](u64* num_entries_out, const std::string& directory,
- const std::string& virtual_name) -> bool {
+void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_path,
+ unsigned int recursion) {
+ const auto callback = [this, target, recursion](u64* num_entries_out,
+ const std::string& directory,
+ const std::string& virtual_name) -> bool {
if (stop_processing) {
// Breaks the callback loop.
return false;
@@ -198,7 +274,8 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
const bool is_dir = FileUtil::IsDirectory(physical_name);
if (!is_dir &&
(HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) {
- auto loader = Loader::GetLoader(vfs->OpenFile(physical_name, FileSys::Mode::Read));
+ const auto file = vfs->OpenFile(physical_name, FileSys::Mode::Read);
+ auto loader = Loader::GetLoader(file);
if (!loader) {
return true;
}
@@ -209,31 +286,42 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
return true;
}
- std::vector<u8> icon;
- const auto res1 = loader->ReadIcon(icon);
-
u64 program_id = 0;
const auto res2 = loader->ReadProgramId(program_id);
- std::string name = " ";
- const auto res3 = loader->ReadTitle(name);
+ if (target == ScanTarget::FillManualContentProvider) {
+ if (res2 == Loader::ResultStatus::Success && file_type == Loader::FileType::NCA) {
+ provider->AddEntry(FileSys::TitleType::Application,
+ FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()),
+ program_id, file);
+ } else if (res2 == Loader::ResultStatus::Success &&
+ (file_type == Loader::FileType::XCI ||
+ file_type == Loader::FileType::NSP)) {
+ const auto nsp = file_type == Loader::FileType::NSP
+ ? std::make_shared<FileSys::NSP>(file)
+ : FileSys::XCI{file}.GetSecurePartitionNSP();
+ for (const auto& title : nsp->GetNCAs()) {
+ for (const auto& entry : title.second) {
+ provider->AddEntry(entry.first.first, entry.first.second, title.first,
+ entry.second->GetBaseFile());
+ }
+ }
+ }
+ } else {
+ std::vector<u8> icon;
+ const auto res1 = loader->ReadIcon(icon);
- const FileSys::PatchManager patch{program_id};
+ std::string name = " ";
+ const auto res3 = loader->ReadTitle(name);
- if (res1 != Loader::ResultStatus::Success && res3 != Loader::ResultStatus::Success &&
- res2 == Loader::ResultStatus::Success) {
- // Use from metadata pool.
- if (nca_control_map.find(program_id) != nca_control_map.end()) {
- const auto& nca = nca_control_map[program_id];
- GetMetadataFromControlNCA(patch, *nca, icon, name);
- }
- }
+ const FileSys::PatchManager patch{program_id};
- emit EntryReady(MakeGameListEntry(physical_name, name, icon, *loader, program_id,
- compatibility_list, patch));
+ emit EntryReady(MakeGameListEntry(physical_name, name, icon, *loader, program_id,
+ compatibility_list, patch));
+ }
} else if (is_dir && recursion > 0) {
watch_list.append(QString::fromStdString(physical_name));
- AddFstEntriesToGameList(physical_name, recursion - 1);
+ ScanFileSystem(target, physical_name, recursion - 1);
}
return true;
@@ -245,10 +333,11 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
void GameListWorker::run() {
stop_processing = false;
watch_list.append(dir_path);
- FillControlMap(dir_path.toStdString());
- AddInstalledTitlesToGameList();
- AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0);
- nca_control_map.clear();
+ provider->ClearAllEntries();
+ ScanFileSystem(ScanTarget::FillManualContentProvider, dir_path.toStdString(),
+ deep_scan ? 256 : 0);
+ AddTitlesToGameList();
+ ScanFileSystem(ScanTarget::PopulateGameList, dir_path.toStdString(), deep_scan ? 256 : 0);
emit Finished(watch_list);
}
diff --git a/src/yuzu/game_list_worker.h b/src/yuzu/game_list_worker.h
index 0e42d0bde..7c3074af9 100644
--- a/src/yuzu/game_list_worker.h
+++ b/src/yuzu/game_list_worker.h
@@ -33,7 +33,8 @@ class GameListWorker : public QObject, public QRunnable {
Q_OBJECT
public:
- GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs, QString dir_path, bool deep_scan,
+ GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs,
+ FileSys::ManualContentProvider* provider, QString dir_path, bool deep_scan,
const CompatibilityList& compatibility_list);
~GameListWorker() override;
@@ -58,12 +59,17 @@ signals:
void Finished(QStringList watch_list);
private:
- void AddInstalledTitlesToGameList();
- void FillControlMap(const std::string& dir_path);
- void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion = 0);
+ void AddTitlesToGameList();
+
+ enum class ScanTarget {
+ FillManualContentProvider,
+ PopulateGameList,
+ };
+
+ void ScanFileSystem(ScanTarget target, const std::string& dir_path, unsigned int recursion = 0);
std::shared_ptr<FileSys::VfsFilesystem> vfs;
- std::map<u64, std::unique_ptr<FileSys::NCA>> nca_control_map;
+ FileSys::ManualContentProvider* provider;
QStringList watch_list;
QString dir_path;
bool deep_scan;
diff --git a/src/yuzu/hotkeys.cpp b/src/yuzu/hotkeys.cpp
index dce399774..4582e7f21 100644
--- a/src/yuzu/hotkeys.cpp
+++ b/src/yuzu/hotkeys.cpp
@@ -2,7 +2,6 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include <map>
#include <QKeySequence>
#include <QShortcut>
#include <QTreeWidgetItem>
@@ -13,47 +12,32 @@
HotkeyRegistry::HotkeyRegistry() = default;
HotkeyRegistry::~HotkeyRegistry() = default;
-void HotkeyRegistry::LoadHotkeys() {
- // Make sure NOT to use a reference here because it would become invalid once we call
- // beginGroup()
- for (auto shortcut : UISettings::values.shortcuts) {
- const QStringList cat = shortcut.first.split('/');
- Q_ASSERT(cat.size() >= 2);
-
- // RegisterHotkey assigns default keybindings, so use old values as default parameters
- Hotkey& hk = hotkey_groups[cat[0]][cat[1]];
- if (!shortcut.second.first.isEmpty()) {
- hk.keyseq = QKeySequence::fromString(shortcut.second.first);
- hk.context = static_cast<Qt::ShortcutContext>(shortcut.second.second);
- }
- if (hk.shortcut)
- hk.shortcut->setKey(hk.keyseq);
- }
-}
-
void HotkeyRegistry::SaveHotkeys() {
UISettings::values.shortcuts.clear();
for (const auto& group : hotkey_groups) {
for (const auto& hotkey : group.second) {
- UISettings::values.shortcuts.emplace_back(
- UISettings::Shortcut(group.first + '/' + hotkey.first,
- UISettings::ContextualShortcut(hotkey.second.keyseq.toString(),
- hotkey.second.context)));
+ UISettings::values.shortcuts.push_back(
+ {hotkey.first, group.first,
+ UISettings::ContextualShortcut(hotkey.second.keyseq.toString(),
+ hotkey.second.context)});
}
}
}
-void HotkeyRegistry::RegisterHotkey(const QString& group, const QString& action,
- const QKeySequence& default_keyseq,
- Qt::ShortcutContext default_context) {
- auto& hotkey_group = hotkey_groups[group];
- if (hotkey_group.find(action) != hotkey_group.end()) {
- return;
+void HotkeyRegistry::LoadHotkeys() {
+ // Make sure NOT to use a reference here because it would become invalid once we call
+ // beginGroup()
+ for (auto shortcut : UISettings::values.shortcuts) {
+ Hotkey& hk = hotkey_groups[shortcut.group][shortcut.name];
+ if (!shortcut.shortcut.first.isEmpty()) {
+ hk.keyseq = QKeySequence::fromString(shortcut.shortcut.first, QKeySequence::NativeText);
+ hk.context = static_cast<Qt::ShortcutContext>(shortcut.shortcut.second);
+ }
+ if (hk.shortcut) {
+ hk.shortcut->disconnect();
+ hk.shortcut->setKey(hk.keyseq);
+ }
}
-
- auto& hotkey_action = hotkey_groups[group][action];
- hotkey_action.keyseq = default_keyseq;
- hotkey_action.context = default_context;
}
QShortcut* HotkeyRegistry::GetHotkey(const QString& group, const QString& action, QWidget* widget) {
@@ -65,24 +49,11 @@ QShortcut* HotkeyRegistry::GetHotkey(const QString& group, const QString& action
return hk.shortcut;
}
-GHotkeysDialog::GHotkeysDialog(QWidget* parent) : QWidget(parent) {
- ui.setupUi(this);
+QKeySequence HotkeyRegistry::GetKeySequence(const QString& group, const QString& action) {
+ return hotkey_groups[group][action].keyseq;
}
-void GHotkeysDialog::Populate(const HotkeyRegistry& registry) {
- for (const auto& group : registry.hotkey_groups) {
- QTreeWidgetItem* toplevel_item = new QTreeWidgetItem(QStringList(group.first));
- for (const auto& hotkey : group.second) {
- QStringList columns;
- columns << hotkey.first << hotkey.second.keyseq.toString();
- QTreeWidgetItem* item = new QTreeWidgetItem(columns);
- toplevel_item->addChild(item);
- }
- ui.treeWidget->addTopLevelItem(toplevel_item);
- }
- // TODO: Make context configurable as well (hiding the column for now)
- ui.treeWidget->setColumnCount(2);
-
- ui.treeWidget->resizeColumnToContents(0);
- ui.treeWidget->resizeColumnToContents(1);
+Qt::ShortcutContext HotkeyRegistry::GetShortcutContext(const QString& group,
+ const QString& action) {
+ return hotkey_groups[group][action].context;
}
diff --git a/src/yuzu/hotkeys.h b/src/yuzu/hotkeys.h
index f38e6c002..248fadaf3 100644
--- a/src/yuzu/hotkeys.h
+++ b/src/yuzu/hotkeys.h
@@ -5,7 +5,6 @@
#pragma once
#include <map>
-#include "ui_hotkeys.h"
class QDialog;
class QKeySequence;
@@ -14,7 +13,7 @@ class QShortcut;
class HotkeyRegistry final {
public:
- friend class GHotkeysDialog;
+ friend class ConfigureHotkeys;
explicit HotkeyRegistry();
~HotkeyRegistry();
@@ -49,19 +48,22 @@ public:
QShortcut* GetHotkey(const QString& group, const QString& action, QWidget* widget);
/**
- * Register a hotkey.
+ * Returns a QKeySequence object whose signal can be connected to QAction::setShortcut.
*
- * @param group General group this hotkey belongs to (e.g. "Main Window", "Debugger")
- * @param action Name of the action (e.g. "Start Emulation", "Load Image")
- * @param default_keyseq Default key sequence to assign if the hotkey wasn't present in the
- * settings file before
- * @param default_context Default context to assign if the hotkey wasn't present in the settings
- * file before
- * @warning Both the group and action strings will be displayed in the hotkey settings dialog
+ * @param group General group this hotkey belongs to (e.g. "Main Window", "Debugger").
+ * @param action Name of the action (e.g. "Start Emulation", "Load Image").
+ */
+ QKeySequence GetKeySequence(const QString& group, const QString& action);
+
+ /**
+ * Returns a Qt::ShortcutContext object who can be connected to other
+ * QAction::setShortcutContext.
+ *
+ * @param group General group this shortcut context belongs to (e.g. "Main Window",
+ * "Debugger").
+ * @param action Name of the action (e.g. "Start Emulation", "Load Image").
*/
- void RegisterHotkey(const QString& group, const QString& action,
- const QKeySequence& default_keyseq = {},
- Qt::ShortcutContext default_context = Qt::WindowShortcut);
+ Qt::ShortcutContext GetShortcutContext(const QString& group, const QString& action);
private:
struct Hotkey {
@@ -75,15 +77,3 @@ private:
HotkeyGroupMap hotkey_groups;
};
-
-class GHotkeysDialog : public QWidget {
- Q_OBJECT
-
-public:
- explicit GHotkeysDialog(QWidget* parent = nullptr);
-
- void Populate(const HotkeyRegistry& registry);
-
-private:
- Ui::hotkeys ui;
-};
diff --git a/src/yuzu/hotkeys.ui b/src/yuzu/hotkeys.ui
deleted file mode 100644
index 050fe064e..000000000
--- a/src/yuzu/hotkeys.ui
+++ /dev/null
@@ -1,46 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>hotkeys</class>
- <widget class="QWidget" name="hotkeys">
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>363</width>
- <height>388</height>
- </rect>
- </property>
- <property name="windowTitle">
- <string>Hotkey Settings</string>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout">
- <item>
- <widget class="QTreeWidget" name="treeWidget">
- <property name="selectionBehavior">
- <enum>QAbstractItemView::SelectItems</enum>
- </property>
- <property name="headerHidden">
- <bool>false</bool>
- </property>
- <column>
- <property name="text">
- <string>Action</string>
- </property>
- </column>
- <column>
- <property name="text">
- <string>Hotkey</string>
- </property>
- </column>
- <column>
- <property name="text">
- <string>Context</string>
- </property>
- </column>
- </widget>
- </item>
- </layout>
- </widget>
- <resources/>
- <connections/>
-</ui>
diff --git a/src/yuzu/loading_screen.cpp b/src/yuzu/loading_screen.cpp
index 86f6d0165..4f2bfab48 100644
--- a/src/yuzu/loading_screen.cpp
+++ b/src/yuzu/loading_screen.cpp
@@ -30,11 +30,11 @@
#include <QMovie>
#endif
-constexpr const char PROGRESSBAR_STYLE_PREPARE[] = R"(
+constexpr char PROGRESSBAR_STYLE_PREPARE[] = R"(
QProgressBar {}
QProgressBar::chunk {})";
-constexpr const char PROGRESSBAR_STYLE_DECOMPILE[] = R"(
+constexpr char PROGRESSBAR_STYLE_DECOMPILE[] = R"(
QProgressBar {
background-color: black;
border: 2px solid white;
@@ -46,7 +46,7 @@ QProgressBar::chunk {
width: 1px;
})";
-constexpr const char PROGRESSBAR_STYLE_BUILD[] = R"(
+constexpr char PROGRESSBAR_STYLE_BUILD[] = R"(
QProgressBar {
background-color: black;
border: 2px solid white;
@@ -58,7 +58,7 @@ QProgressBar::chunk {
width: 1px;
})";
-constexpr const char PROGRESSBAR_STYLE_COMPLETE[] = R"(
+constexpr char PROGRESSBAR_STYLE_COMPLETE[] = R"(
QProgressBar {
background-color: #0ab9e6;
border: 2px solid white;
@@ -149,10 +149,10 @@ void LoadingScreen::OnLoadComplete() {
void LoadingScreen::OnLoadProgress(VideoCore::LoadCallbackStage stage, std::size_t value,
std::size_t total) {
using namespace std::chrono;
- auto now = high_resolution_clock::now();
+ const auto now = high_resolution_clock::now();
// reset the timer if the stage changes
if (stage != previous_stage) {
- ui->progress_bar->setStyleSheet(progressbar_style[stage]);
+ ui->progress_bar->setStyleSheet(QString::fromUtf8(progressbar_style[stage]));
// Hide the progress bar during the prepare stage
if (stage == VideoCore::LoadCallbackStage::Prepare) {
ui->progress_bar->hide();
@@ -178,21 +178,26 @@ void LoadingScreen::OnLoadProgress(VideoCore::LoadCallbackStage stage, std::size
slow_shader_first_value = value;
}
// only calculate an estimate time after a second has passed since stage change
- auto diff = duration_cast<milliseconds>(now - slow_shader_start);
+ const auto diff = duration_cast<milliseconds>(now - slow_shader_start);
if (diff > seconds{1}) {
- auto eta_mseconds =
+ const auto eta_mseconds =
static_cast<long>(static_cast<double>(total - slow_shader_first_value) /
(value - slow_shader_first_value) * diff.count());
estimate =
tr("Estimated Time %1")
.arg(QTime(0, 0, 0, 0)
.addMSecs(std::max<long>(eta_mseconds - diff.count() + 1000, 1000))
- .toString("mm:ss"));
+ .toString(QStringLiteral("mm:ss")));
}
}
// update labels and progress bar
- ui->stage->setText(stage_translations[stage].arg(value).arg(total));
+ if (stage == VideoCore::LoadCallbackStage::Decompile ||
+ stage == VideoCore::LoadCallbackStage::Build) {
+ ui->stage->setText(stage_translations[stage].arg(value).arg(total));
+ } else {
+ ui->stage->setText(stage_translations[stage]);
+ }
ui->value->setText(estimate);
ui->progress_bar->setValue(static_cast<int>(value));
previous_time = now;
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 0f5a14841..66a7080c9 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -8,12 +8,15 @@
#include <thread>
// VFS includes must be before glad as they will conflict with Windows file api, which uses defines.
+#include "applets/error.h"
#include "applets/profile_select.h"
#include "applets/software_keyboard.h"
#include "applets/web_browser.h"
+#include "configuration/configure_input.h"
#include "configuration/configure_per_general.h"
#include "core/file_sys/vfs.h"
#include "core/file_sys/vfs_real.h"
+#include "core/frontend/applets/general_frontend.h"
#include "core/frontend/scope_acquire_window_context.h"
#include "core/hle/service/acc/profile_manager.h"
#include "core/hle/service/am/applets/applets.h"
@@ -36,14 +39,20 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include <glad/glad.h>
#define QT_NO_OPENGL
+#include <QClipboard>
+#include <QDesktopServices>
#include <QDesktopWidget>
#include <QDialogButtonBox>
#include <QFile>
#include <QFileDialog>
+#include <QInputDialog>
#include <QMessageBox>
+#include <QProgressBar>
+#include <QProgressDialog>
+#include <QShortcut>
+#include <QStatusBar>
#include <QtConcurrent/QtConcurrent>
-#include <QtGui>
-#include <QtWidgets>
+
#include <fmt/format.h>
#include "common/common_paths.h"
#include "common/detached_tasks.h"
@@ -54,11 +63,9 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#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/bis_factory.h"
#include "core/file_sys/card_image.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/control_metadata.h"
@@ -70,7 +77,6 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "core/frontend/applets/software_keyboard.h"
#include "core/hle/kernel/process.h"
#include "core/hle/service/filesystem/filesystem.h"
-#include "core/hle/service/filesystem/fsp_ldr.h"
#include "core/hle/service/nfp/nfp.h"
#include "core/hle/service/sm/sm.h"
#include "core/loader/loader.h"
@@ -86,7 +92,6 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "yuzu/configuration/configure_dialog.h"
#include "yuzu/debugger/console.h"
#include "yuzu/debugger/graphics/graphics_breakpoints.h"
-#include "yuzu/debugger/graphics/graphics_surface.h"
#include "yuzu/debugger/profiler.h"
#include "yuzu/debugger/wait_tree.h"
#include "yuzu/discord.h"
@@ -167,7 +172,8 @@ static void InitializeLogging() {
GMainWindow::GMainWindow()
: config(new Config()), emu_thread(nullptr),
- vfs(std::make_shared<FileSys::RealVfsFilesystem>()) {
+ vfs(std::make_shared<FileSys::RealVfsFilesystem>()),
+ provider(std::make_unique<FileSys::ManualContentProvider>()) {
InitializeLogging();
debug_context = Tegra::DebugContext::Construct();
@@ -192,20 +198,25 @@ GMainWindow::GMainWindow()
ConnectMenuEvents();
ConnectWidgetEvents();
+
LOG_INFO(Frontend, "yuzu Version: {} | {}-{}", Common::g_build_fullname, Common::g_scm_branch,
Common::g_scm_desc);
+ UpdateWindowTitle();
- setWindowTitle(QString("yuzu %1| %2-%3")
- .arg(Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc));
show();
+ Core::System::GetInstance().SetContentProvider(
+ std::make_unique<FileSys::ContentProviderUnion>());
+ Core::System::GetInstance().RegisterContentProvider(
+ FileSys::ContentProviderUnionSlot::FrontendManual, provider.get());
+ Service::FileSystem::CreateFactories(*vfs);
+
// Gen keys if necessary
OnReinitializeKeys(ReinitializeKeyBehavior::NoWarning);
- // Necessary to load titles from nand in gamelist.
- Service::FileSystem::CreateFactories(*vfs);
game_list->LoadCompatibilityList();
- game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
+ game_list->PopulateAsync(UISettings::values.game_directory_path,
+ UISettings::values.game_directory_deepscan);
// Show one-time "callout" messages to the user
ShowTelemetryCallout();
@@ -227,15 +238,13 @@ void GMainWindow::ProfileSelectorSelectProfile() {
dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint);
dialog.setWindowModality(Qt::WindowModal);
- dialog.exec();
-
- if (!dialog.GetStatus()) {
+ if (dialog.exec() == QDialog::Rejected) {
emit ProfileSelectorFinishedSelection(std::nullopt);
return;
}
Service::Account::ProfileManager manager;
- const auto uuid = manager.GetUser(dialog.GetIndex());
+ const auto uuid = manager.GetUser(static_cast<std::size_t>(dialog.GetIndex()));
if (!uuid.has_value()) {
emit ProfileSelectorFinishedSelection(std::nullopt);
return;
@@ -250,9 +259,8 @@ void GMainWindow::SoftwareKeyboardGetText(
dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint);
dialog.setWindowModality(Qt::WindowModal);
- dialog.exec();
- if (!dialog.GetStatus()) {
+ if (dialog.exec() == QDialog::Rejected) {
emit SoftwareKeyboardFinishedText(std::nullopt);
return;
}
@@ -270,7 +278,7 @@ void GMainWindow::SoftwareKeyboardInvokeCheckDialog(std::u16string error_message
void GMainWindow::WebBrowserOpenPage(std::string_view filename, std::string_view additional_args) {
NXInputWebEngineView web_browser_view(this);
- // Scope to contain the QProgressDialog for initalization
+ // Scope to contain the QProgressDialog for initialization
{
QProgressDialog progress(this);
progress.setMinimumDuration(200);
@@ -290,7 +298,7 @@ void GMainWindow::WebBrowserOpenPage(std::string_view filename, std::string_view
QWebEngineScript nx_shim;
nx_shim.setSourceCode(GetNXShimInjectionScript());
nx_shim.setWorldId(QWebEngineScript::MainWorld);
- nx_shim.setName("nx_inject.js");
+ 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);
@@ -336,9 +344,14 @@ void GMainWindow::WebBrowserOpenPage(std::string_view filename, std::string_view
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(QString::fromStdString(std::to_string(key_code))));
+ .arg(key_code));
};
+ 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();
@@ -410,7 +423,7 @@ void GMainWindow::InitializeWidgets() {
render_window = new GRenderWindow(this, emu_thread.get());
render_window->hide();
- game_list = new GameList(vfs, this);
+ game_list = new GameList(vfs, provider.get(), this);
ui.horizontalLayout->addWidget(game_list);
loading_screen = new LoadingScreen(this);
@@ -452,7 +465,7 @@ void GMainWindow::InitializeWidgets() {
statusBar()->addPermanentWidget(label, 0);
}
statusBar()->setVisible(true);
- setStyleSheet("QStatusBar::item{border: none;}");
+ setStyleSheet(QStringLiteral("QStatusBar::item{border: none;}"));
}
void GMainWindow::InitializeDebugWidgets() {
@@ -469,11 +482,6 @@ void GMainWindow::InitializeDebugWidgets() {
graphicsBreakpointsWidget->hide();
debug_menu->addAction(graphicsBreakpointsWidget->toggleViewAction());
- graphicsSurfaceWidget = new GraphicsSurfaceWidget(debug_context, this);
- addDockWidget(Qt::RightDockWidgetArea, graphicsSurfaceWidget);
- graphicsSurfaceWidget->hide();
- debug_menu->addAction(graphicsSurfaceWidget->toggleViewAction());
-
waitTreeWidget = new WaitTreeWidget(this);
addDockWidget(Qt::LeftDockWidgetArea, waitTreeWidget);
waitTreeWidget->hide();
@@ -505,58 +513,69 @@ void GMainWindow::InitializeRecentFileMenuActions() {
}
void GMainWindow::InitializeHotkeys() {
- hotkey_registry.RegisterHotkey("Main Window", "Load File", QKeySequence::Open);
- hotkey_registry.RegisterHotkey("Main Window", "Start Emulation");
- hotkey_registry.RegisterHotkey("Main Window", "Continue/Pause", QKeySequence(Qt::Key_F4));
- hotkey_registry.RegisterHotkey("Main Window", "Restart", QKeySequence(Qt::Key_F5));
- hotkey_registry.RegisterHotkey("Main Window", "Fullscreen", QKeySequence::FullScreen);
- hotkey_registry.RegisterHotkey("Main Window", "Exit Fullscreen", QKeySequence(Qt::Key_Escape),
- Qt::ApplicationShortcut);
- hotkey_registry.RegisterHotkey("Main Window", "Toggle Speed Limit", QKeySequence("CTRL+Z"),
- Qt::ApplicationShortcut);
- hotkey_registry.RegisterHotkey("Main Window", "Increase Speed Limit", QKeySequence("+"),
- Qt::ApplicationShortcut);
- hotkey_registry.RegisterHotkey("Main Window", "Decrease Speed Limit", QKeySequence("-"),
- Qt::ApplicationShortcut);
- hotkey_registry.RegisterHotkey("Main Window", "Load Amiibo", QKeySequence(Qt::Key_F2),
- Qt::ApplicationShortcut);
- hotkey_registry.RegisterHotkey("Main Window", "Capture Screenshot",
- QKeySequence(QKeySequence::Print));
-
hotkey_registry.LoadHotkeys();
- connect(hotkey_registry.GetHotkey("Main Window", "Load File", this), &QShortcut::activated,
- this, &GMainWindow::OnMenuLoadFile);
- connect(hotkey_registry.GetHotkey("Main Window", "Start Emulation", this),
- &QShortcut::activated, this, &GMainWindow::OnStartGame);
- connect(hotkey_registry.GetHotkey("Main Window", "Continue/Pause", this), &QShortcut::activated,
- this, [&] {
- if (emulation_running) {
- if (emu_thread->IsRunning()) {
- OnPauseGame();
- } else {
- OnStartGame();
- }
+ const QString main_window = QStringLiteral("Main Window");
+ const QString load_file = QStringLiteral("Load File");
+ const QString exit_yuzu = QStringLiteral("Exit yuzu");
+ const QString stop_emulation = QStringLiteral("Stop Emulation");
+ const QString toggle_filter_bar = QStringLiteral("Toggle Filter Bar");
+ const QString toggle_status_bar = QStringLiteral("Toggle Status Bar");
+ const QString fullscreen = QStringLiteral("Fullscreen");
+
+ ui.action_Load_File->setShortcut(hotkey_registry.GetKeySequence(main_window, load_file));
+ ui.action_Load_File->setShortcutContext(
+ hotkey_registry.GetShortcutContext(main_window, load_file));
+
+ ui.action_Exit->setShortcut(hotkey_registry.GetKeySequence(main_window, exit_yuzu));
+ ui.action_Exit->setShortcutContext(hotkey_registry.GetShortcutContext(main_window, exit_yuzu));
+
+ ui.action_Stop->setShortcut(hotkey_registry.GetKeySequence(main_window, stop_emulation));
+ ui.action_Stop->setShortcutContext(
+ hotkey_registry.GetShortcutContext(main_window, stop_emulation));
+
+ ui.action_Show_Filter_Bar->setShortcut(
+ hotkey_registry.GetKeySequence(main_window, toggle_filter_bar));
+ ui.action_Show_Filter_Bar->setShortcutContext(
+ hotkey_registry.GetShortcutContext(main_window, toggle_filter_bar));
+
+ ui.action_Show_Status_Bar->setShortcut(
+ hotkey_registry.GetKeySequence(main_window, toggle_status_bar));
+ ui.action_Show_Status_Bar->setShortcutContext(
+ hotkey_registry.GetShortcutContext(main_window, toggle_status_bar));
+
+ connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Load File"), this),
+ &QShortcut::activated, this, &GMainWindow::OnMenuLoadFile);
+ connect(
+ hotkey_registry.GetHotkey(main_window, QStringLiteral("Continue/Pause Emulation"), this),
+ &QShortcut::activated, this, [&] {
+ if (emulation_running) {
+ if (emu_thread->IsRunning()) {
+ OnPauseGame();
+ } else {
+ OnStartGame();
}
- });
- connect(hotkey_registry.GetHotkey("Main Window", "Restart", this), &QShortcut::activated, this,
- [this] {
- if (!Core::System::GetInstance().IsPoweredOn())
+ }
+ });
+ connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Restart Emulation"), this),
+ &QShortcut::activated, this, [this] {
+ if (!Core::System::GetInstance().IsPoweredOn()) {
return;
- BootGame(QString(game_path));
+ }
+ BootGame(game_path);
});
- connect(hotkey_registry.GetHotkey("Main Window", "Fullscreen", render_window),
+ connect(hotkey_registry.GetHotkey(main_window, fullscreen, render_window),
&QShortcut::activated, ui.action_Fullscreen, &QAction::trigger);
- connect(hotkey_registry.GetHotkey("Main Window", "Fullscreen", render_window),
+ connect(hotkey_registry.GetHotkey(main_window, fullscreen, render_window),
&QShortcut::activatedAmbiguously, ui.action_Fullscreen, &QAction::trigger);
- connect(hotkey_registry.GetHotkey("Main Window", "Exit Fullscreen", this),
+ connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Exit Fullscreen"), this),
&QShortcut::activated, this, [&] {
if (emulation_running) {
ui.action_Fullscreen->setChecked(false);
ToggleFullscreen();
}
});
- connect(hotkey_registry.GetHotkey("Main Window", "Toggle Speed Limit", this),
+ connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Toggle Speed Limit"), this),
&QShortcut::activated, this, [&] {
Settings::values.use_frame_limit = !Settings::values.use_frame_limit;
UpdateStatusBar();
@@ -565,32 +584,38 @@ void GMainWindow::InitializeHotkeys() {
// MSVC occurs and we make it a requirement (see:
// https://developercommunity.visualstudio.com/content/problem/93922/constexprs-are-trying-to-be-captured-in-lambda-fun.html)
static constexpr u16 SPEED_LIMIT_STEP = 5;
- connect(hotkey_registry.GetHotkey("Main Window", "Increase Speed Limit", this),
+ connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Increase Speed Limit"), this),
&QShortcut::activated, this, [&] {
if (Settings::values.frame_limit < 9999 - SPEED_LIMIT_STEP) {
Settings::values.frame_limit += SPEED_LIMIT_STEP;
UpdateStatusBar();
}
});
- connect(hotkey_registry.GetHotkey("Main Window", "Decrease Speed Limit", this),
+ connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Decrease Speed Limit"), this),
&QShortcut::activated, this, [&] {
if (Settings::values.frame_limit > SPEED_LIMIT_STEP) {
Settings::values.frame_limit -= SPEED_LIMIT_STEP;
UpdateStatusBar();
}
});
- connect(hotkey_registry.GetHotkey("Main Window", "Load Amiibo", this), &QShortcut::activated,
- this, [&] {
+ connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Load Amiibo"), this),
+ &QShortcut::activated, this, [&] {
if (ui.action_Load_Amiibo->isEnabled()) {
OnLoadAmiibo();
}
});
- connect(hotkey_registry.GetHotkey("Main Window", "Capture Screenshot", this),
+ connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Capture Screenshot"), this),
&QShortcut::activated, this, [&] {
if (emu_thread->IsRunning()) {
OnCaptureScreenshot();
}
});
+ 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);
+ });
}
void GMainWindow::SetDefaultUIGeometry() {
@@ -635,6 +660,8 @@ void GMainWindow::RestoreUIState() {
void GMainWindow::ConnectWidgetEvents() {
connect(game_list, &GameList::GameChosen, this, &GMainWindow::OnGameListLoadFile);
connect(game_list, &GameList::OpenFolderRequested, this, &GMainWindow::OnGameListOpenFolder);
+ connect(game_list, &GameList::OpenTransferableShaderCacheRequested, this,
+ &GMainWindow::OnTransferableShaderCacheOpenFile);
connect(game_list, &GameList::DumpRomFSRequested, this, &GMainWindow::OnGameListDumpRomFS);
connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID);
connect(game_list, &GameList::NavigateToGamedbEntryRequested, this,
@@ -679,13 +706,14 @@ void GMainWindow::ConnectMenuEvents() {
&GMainWindow::ToggleWindowMode);
connect(ui.action_Display_Dock_Widget_Headers, &QAction::triggered, this,
&GMainWindow::OnDisplayTitleBars);
- ui.action_Show_Filter_Bar->setShortcut(tr("CTRL+F"));
connect(ui.action_Show_Filter_Bar, &QAction::triggered, this, &GMainWindow::OnToggleFilterBar);
connect(ui.action_Show_Status_Bar, &QAction::triggered, statusBar(), &QStatusBar::setVisible);
// Fullscreen
ui.action_Fullscreen->setShortcut(
- hotkey_registry.GetHotkey("Main Window", "Fullscreen", this)->key());
+ hotkey_registry
+ .GetHotkey(QStringLiteral("Main Window"), QStringLiteral("Fullscreen"), this)
+ ->key());
connect(ui.action_Fullscreen, &QAction::triggered, this, &GMainWindow::ToggleFullscreen);
// Movie
@@ -722,25 +750,33 @@ void GMainWindow::OnDisplayTitleBars(bool show) {
QStringList GMainWindow::GetUnsupportedGLExtensions() {
QStringList unsupported_ext;
- if (!GLAD_GL_ARB_direct_state_access)
- unsupported_ext.append("ARB_direct_state_access");
- if (!GLAD_GL_ARB_vertex_type_10f_11f_11f_rev)
- unsupported_ext.append("ARB_vertex_type_10f_11f_11f_rev");
- if (!GLAD_GL_ARB_texture_mirror_clamp_to_edge)
- unsupported_ext.append("ARB_texture_mirror_clamp_to_edge");
- if (!GLAD_GL_ARB_multi_bind)
- unsupported_ext.append("ARB_multi_bind");
+ 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"));
+ }
// Extensions required to support some texture formats.
- if (!GLAD_GL_EXT_texture_compression_s3tc)
- unsupported_ext.append("EXT_texture_compression_s3tc");
- if (!GLAD_GL_ARB_texture_compression_rgtc)
- unsupported_ext.append("ARB_texture_compression_rgtc");
- if (!GLAD_GL_ARB_depth_buffer_float)
- unsupported_ext.append("ARB_depth_buffer_float");
-
- for (const QString& ext : unsupported_ext)
+ if (!GLAD_GL_EXT_texture_compression_s3tc) {
+ unsupported_ext.append(QStringLiteral("EXT_texture_compression_s3tc"));
+ }
+ 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());
+ }
return unsupported_ext;
}
@@ -762,13 +798,13 @@ bool GMainWindow::LoadROM(const QString& filename) {
}
}
- QStringList unsupported_gl_extensions = GetUnsupportedGLExtensions();
+ const QStringList unsupported_gl_extensions = GetUnsupportedGLExtensions();
if (!unsupported_gl_extensions.empty()) {
QMessageBox::critical(this, tr("Error while initializing OpenGL Core!"),
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("<br>"));
+ unsupported_gl_extensions.join(QStringLiteral("<br>")));
return false;
}
@@ -777,9 +813,13 @@ bool GMainWindow::LoadROM(const QString& filename) {
system.SetGPUDebugContext(debug_context);
- system.SetProfileSelector(std::make_unique<QtProfileSelector>(*this));
- system.SetSoftwareKeyboard(std::make_unique<QtSoftwareKeyboard>(*this));
- system.SetWebBrowser(std::make_unique<QtWebBrowser>(*this));
+ system.SetAppletFrontendSet({
+ std::make_unique<QtErrorDisplay>(*this),
+ nullptr,
+ std::make_unique<QtProfileSelector>(*this),
+ std::make_unique<QtSoftwareKeyboard>(*this),
+ std::make_unique<QtWebBrowser>(*this),
+ });
const Core::System::ResultStatus result{system.Load(*render_window, filename.toStdString())};
@@ -807,11 +847,6 @@ bool GMainWindow::LoadROM(const QString& filename) {
QMessageBox::critical(this, tr("Error while loading ROM!"),
tr("The ROM format is not supported."));
break;
- case Core::System::ResultStatus::ErrorSystemMode:
- LOG_CRITICAL(Frontend, "Failed to load ROM!");
- QMessageBox::critical(this, tr("Error while loading ROM!"),
- tr("Could not determine the system mode."));
- break;
case Core::System::ResultStatus::ErrorVideoCore:
QMessageBox::critical(
this, tr("An error occurred initializing the video core."),
@@ -858,11 +893,12 @@ void GMainWindow::SelectAndSetCurrentUser() {
dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint);
dialog.setWindowModality(Qt::WindowModal);
- dialog.exec();
- if (dialog.GetStatus()) {
- Settings::values.current_user = static_cast<s32>(dialog.GetIndex());
+ if (dialog.exec() == QDialog::Rejected) {
+ return;
}
+
+ Settings::values.current_user = dialog.GetIndex();
}
void GMainWindow::BootGame(const QString& filename) {
@@ -912,9 +948,7 @@ void GMainWindow::BootGame(const QString& filename) {
title_name = FileUtil::GetFilename(filename.toStdString());
}
- setWindowTitle(QString("yuzu %1| %4 | %2-%3")
- .arg(Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc,
- QString::fromStdString(title_name)));
+ UpdateWindowTitle(QString::fromStdString(title_name));
loading_screen->Prepare(Core::System::GetInstance().GetAppLoader());
loading_screen->show();
@@ -955,8 +989,8 @@ void GMainWindow::ShutdownGame() {
loading_screen->Clear();
game_list->show();
game_list->setFilterFocus();
- setWindowTitle(QString("yuzu %1| %2-%3")
- .arg(Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc));
+
+ UpdateWindowTitle();
// Disable status bar updates
status_bar_update_timer.stop();
@@ -985,7 +1019,7 @@ void GMainWindow::UpdateRecentFiles() {
std::min(UISettings::values.recent_files.size(), max_recent_files_item);
for (int i = 0; i < num_recent_files; i++) {
- const QString text = QString("&%1. %2").arg(i + 1).arg(
+ const QString text = QStringLiteral("&%1. %2").arg(i + 1).arg(
QFileInfo(UISettings::values.recent_files[i]).fileName());
actions_recent_files[i]->setText(text);
actions_recent_files[i]->setData(UISettings::values.recent_files[i]);
@@ -1007,21 +1041,20 @@ void GMainWindow::OnGameListLoadFile(QString game_path) {
void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target) {
std::string path;
- std::string open_target;
+ QString open_target;
switch (target) {
case GameListOpenTarget::SaveData: {
- open_target = "Save Data";
+ open_target = tr("Save Data");
const std::string nand_dir = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir);
ASSERT(program_id != 0);
- const auto select_profile = [this]() -> s32 {
+ const auto select_profile = [this] {
QtProfileSelectionDialog dialog(this);
dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint);
dialog.setWindowModality(Qt::WindowModal);
- dialog.exec();
- if (!dialog.GetStatus()) {
+ if (dialog.exec() == QDialog::Rejected) {
return -1;
}
@@ -1029,11 +1062,12 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target
};
const auto index = select_profile();
- if (index == -1)
+ if (index == -1) {
return;
+ }
Service::Account::ProfileManager manager;
- const auto user_id = manager.GetUser(index);
+ const auto user_id = manager.GetUser(static_cast<std::size_t>(index));
ASSERT(user_id);
path = nand_dir + FileSys::SaveDataFactory::GetFullPath(FileSys::SaveDataSpaceId::NandUser,
FileSys::SaveDataType::SaveData,
@@ -1047,7 +1081,7 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target
break;
}
case GameListOpenTarget::ModData: {
- open_target = "Mod Data";
+ open_target = tr("Mod Data");
const auto load_dir = FileUtil::GetUserPath(FileUtil::UserPath::LoadDir);
path = fmt::format("{}{:016X}", load_dir, program_id);
break;
@@ -1057,18 +1091,50 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target
}
const QString qpath = QString::fromStdString(path);
-
const QDir dir(qpath);
if (!dir.exists()) {
- QMessageBox::warning(this,
- tr("Error Opening %1 Folder").arg(QString::fromStdString(open_target)),
+ QMessageBox::warning(this, tr("Error Opening %1 Folder").arg(open_target),
tr("Folder does not exist!"));
return;
}
- LOG_INFO(Frontend, "Opening {} path for program_id={:016x}", open_target, program_id);
+ LOG_INFO(Frontend, "Opening {} path for program_id={:016x}", open_target.toStdString(),
+ program_id);
QDesktopServices::openUrl(QUrl::fromLocalFile(qpath));
}
+void GMainWindow::OnTransferableShaderCacheOpenFile(u64 program_id) {
+ ASSERT(program_id != 0);
+
+ const QString shader_dir =
+ QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir));
+ const QString tranferable_shader_cache_folder_path =
+ shader_dir + QStringLiteral("opengl") + QDir::separator() + QStringLiteral("transferable");
+ const QString transferable_shader_cache_file_path =
+ tranferable_shader_cache_folder_path + QDir::separator() +
+ QString::fromStdString(fmt::format("{:016X}.bin", program_id));
+
+ if (!QFile::exists(transferable_shader_cache_file_path)) {
+ QMessageBox::warning(this, tr("Error Opening Transferable Shader Cache"),
+ tr("A shader cache for this title does not exist."));
+ return;
+ }
+
+ // Windows supports opening a folder with selecting a specified file in explorer. On every other
+ // OS we just open the transferable shader cache folder without preselecting the transferable
+ // shader cache file for the selected game.
+#if defined(Q_OS_WIN)
+ const QString explorer = QStringLiteral("explorer");
+ QStringList param;
+ if (!QFileInfo(transferable_shader_cache_file_path).isDir()) {
+ param << QStringLiteral("/select,");
+ }
+ param << QDir::toNativeSeparators(transferable_shader_cache_file_path);
+ QProcess::startDetached(explorer, param);
+#else
+ QDesktopServices::openUrl(QUrl::fromLocalFile(tranferable_shader_cache_folder_path));
+#endif
+}
+
static std::size_t CalculateRomFSEntrySize(const FileSys::VirtualDir& dir, bool full) {
std::size_t out = 0;
@@ -1128,7 +1194,7 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
return;
}
- const auto installed = Service::FileSystem::GetUnionContents();
+ const auto& installed = Core::System::GetInstance().GetContentProvider();
const auto romfs_title_id = SelectRomFSDumpTarget(installed, program_id);
if (!romfs_title_id) {
@@ -1161,20 +1227,21 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
return;
}
- bool ok;
+ bool ok = false;
+ const QStringList selections{tr("Full"), tr("Skeleton")};
const auto res = QInputDialog::getItem(
this, tr("Select RomFS Dump Mode"),
tr("Please select the how you would like the RomFS dumped.<br>Full will copy all of the "
"files into the new directory while <br>skeleton will only create the directory "
"structure."),
- {"Full", "Skeleton"}, 0, false, &ok);
+ selections, 0, false, &ok);
if (!ok) {
failed();
vfs->DeleteDirectory(path);
return;
}
- const auto full = res == "Full";
+ const auto full = res == selections.constFirst();
const auto entry_size = CalculateRomFSEntrySize(extracted, full);
QProgressDialog progress(tr("Extracting RomFS..."), tr("Cancel"), 0,
@@ -1204,10 +1271,11 @@ void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id,
const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
QString directory;
- if (it != compatibility_list.end())
+ if (it != compatibility_list.end()) {
directory = it->second.second;
+ }
- QDesktopServices::openUrl(QUrl("https://yuzu-emu.org/game/" + directory));
+ QDesktopServices::openUrl(QUrl(QStringLiteral("https://yuzu-emu.org/game/") + directory));
}
void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) {
@@ -1221,15 +1289,15 @@ void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) {
}
ConfigurePerGameGeneral dialog(this, title_id);
- dialog.loadFromFile(v_file);
+ dialog.LoadFromFile(v_file);
auto result = dialog.exec();
if (result == QDialog::Accepted) {
- dialog.applyConfiguration();
+ dialog.ApplyConfiguration();
const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false);
if (reload) {
- game_list->PopulateAsync(UISettings::values.gamedir,
- UISettings::values.gamedir_deepscan);
+ game_list->PopulateAsync(UISettings::values.game_directory_path,
+ UISettings::values.game_directory_deepscan);
}
config->Save();
@@ -1238,7 +1306,9 @@ void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) {
void GMainWindow::OnMenuLoadFile() {
const QString extensions =
- QString("*.").append(GameList::supported_file_extensions.join(" *.")).append(" main");
+ QStringLiteral("*.")
+ .append(GameList::supported_file_extensions.join(QStringLiteral(" *.")))
+ .append(QStringLiteral(" main"));
const QString file_filter = tr("Switch Executable (%1);;All Files (*.*)",
"%1 is an identifier for the Switch executable file extensions.")
.arg(extensions);
@@ -1262,9 +1332,9 @@ void GMainWindow::OnMenuLoadFolder() {
}
const QDir dir{dir_path};
- const QStringList matching_main = dir.entryList(QStringList("main"), QDir::Files);
+ const QStringList matching_main = dir.entryList({QStringLiteral("main")}, QDir::Files);
if (matching_main.size() == 1) {
- BootGame(dir.path() + DIR_SEP + matching_main[0]);
+ BootGame(dir.path() + QDir::separator() + matching_main[0]);
} else {
QMessageBox::warning(this, tr("Invalid Directory Selected"),
tr("The directory you have selected does not contain a 'main' file."));
@@ -1317,7 +1387,10 @@ void GMainWindow::OnMenuInstallToNAND() {
const auto success = [this]() {
QMessageBox::information(this, tr("Successfully Installed"),
tr("The file was successfully installed."));
- game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
+ game_list->PopulateAsync(UISettings::values.game_directory_path,
+ UISettings::values.game_directory_deepscan);
+ FileUtil::DeleteDirRecursively(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) +
+ DIR_SEP + "game_list");
};
const auto failed = [this]() {
@@ -1335,11 +1408,10 @@ void GMainWindow::OnMenuInstallToNAND() {
QMessageBox::Yes;
};
- if (filename.endsWith("xci", Qt::CaseInsensitive) ||
- filename.endsWith("nsp", Qt::CaseInsensitive)) {
-
+ if (filename.endsWith(QStringLiteral("xci"), Qt::CaseInsensitive) ||
+ filename.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) {
std::shared_ptr<FileSys::NSP> nsp;
- if (filename.endsWith("nsp", Qt::CaseInsensitive)) {
+ if (filename.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) {
nsp = std::make_shared<FileSys::NSP>(
vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read));
if (nsp->IsExtractedType())
@@ -1444,8 +1516,8 @@ void GMainWindow::OnMenuInstallToNAND() {
void GMainWindow::OnMenuSelectGameListRoot() {
QString dir_path = QFileDialog::getExistingDirectory(this, tr("Select Directory"));
if (!dir_path.isEmpty()) {
- UISettings::values.gamedir = dir_path;
- game_list->PopulateAsync(dir_path, UISettings::values.gamedir_deepscan);
+ UISettings::values.game_directory_path = dir_path;
+ game_list->PopulateAsync(dir_path, UISettings::values.game_directory_deepscan);
}
}
@@ -1467,7 +1539,8 @@ void GMainWindow::OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target)
: FileUtil::UserPath::NANDDir,
dir_path.toStdString());
Service::FileSystem::CreateFactories(*vfs);
- game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
+ game_list->PopulateAsync(UISettings::values.game_directory_path,
+ UISettings::values.game_directory_deepscan);
}
}
@@ -1530,6 +1603,11 @@ void GMainWindow::OnLoadComplete() {
loading_screen->OnLoadComplete();
}
+void GMainWindow::ErrorDisplayDisplayError(QString body) {
+ QMessageBox::critical(this, tr("Error Display"), body);
+ emit ErrorDisplayFinished();
+}
+
void GMainWindow::OnMenuReportCompatibility() {
if (!Settings::values.yuzu_token.empty() && !Settings::values.yuzu_username.empty()) {
CompatDB compatdb{this};
@@ -1605,31 +1683,37 @@ void GMainWindow::ToggleWindowMode() {
}
void GMainWindow::OnConfigure() {
- ConfigureDialog configureDialog(this, hotkey_registry);
- auto old_theme = UISettings::values.theme;
+ const auto old_theme = UISettings::values.theme;
const bool old_discord_presence = UISettings::values.enable_discord_presence;
- auto result = configureDialog.exec();
- if (result == QDialog::Accepted) {
- configureDialog.applyConfiguration();
- if (UISettings::values.theme != old_theme)
- UpdateUITheme();
- if (UISettings::values.enable_discord_presence != old_discord_presence)
- SetDiscordEnabled(UISettings::values.enable_discord_presence);
- const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false);
- if (reload) {
- game_list->PopulateAsync(UISettings::values.gamedir,
- UISettings::values.gamedir_deepscan);
- }
+ ConfigureDialog configure_dialog(this, hotkey_registry);
+ const auto result = configure_dialog.exec();
+ if (result != QDialog::Accepted) {
+ return;
+ }
- config->Save();
+ configure_dialog.ApplyConfiguration();
+ InitializeHotkeys();
+ if (UISettings::values.theme != old_theme) {
+ UpdateUITheme();
+ }
+ if (UISettings::values.enable_discord_presence != old_discord_presence) {
+ SetDiscordEnabled(UISettings::values.enable_discord_presence);
}
+
+ const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false);
+ if (reload) {
+ game_list->PopulateAsync(UISettings::values.game_directory_path,
+ UISettings::values.game_directory_deepscan);
+ }
+
+ config->Save();
}
void GMainWindow::OnLoadAmiibo() {
- const QString extensions{"*.bin"};
+ const QString extensions{QStringLiteral("*.bin")};
const QString file_filter = tr("Amiibo File (%1);; All Files (*.*)").arg(extensions);
- const QString filename = QFileDialog::getOpenFileName(this, tr("Load Amiibo"), "", file_filter);
+ const QString filename = QFileDialog::getOpenFileName(this, tr("Load Amiibo"), {}, file_filter);
if (filename.isEmpty()) {
return;
@@ -1691,7 +1775,7 @@ void GMainWindow::OnCaptureScreenshot() {
QFileDialog png_dialog(this, tr("Capture Screenshot"), UISettings::values.screenshot_path,
tr("PNG Image (*.png)"));
png_dialog.setAcceptMode(QFileDialog::AcceptSave);
- png_dialog.setDefaultSuffix("png");
+ png_dialog.setDefaultSuffix(QStringLiteral("png"));
if (png_dialog.exec()) {
const QString path = png_dialog.selectedFiles().first();
if (!path.isEmpty()) {
@@ -1702,6 +1786,19 @@ void GMainWindow::OnCaptureScreenshot() {
OnStartGame();
}
+void GMainWindow::UpdateWindowTitle(const QString& title_name) {
+ const QString full_name = QString::fromUtf8(Common::g_build_fullname);
+ const QString branch_name = QString::fromUtf8(Common::g_scm_branch);
+ const QString description = QString::fromUtf8(Common::g_scm_desc);
+
+ if (title_name.isEmpty()) {
+ setWindowTitle(QStringLiteral("yuzu %1| %2-%3").arg(full_name, branch_name, description));
+ } else {
+ setWindowTitle(QStringLiteral("yuzu %1| %4 | %2-%3")
+ .arg(full_name, branch_name, description, title_name));
+ }
+}
+
void GMainWindow::UpdateStatusBar() {
if (emu_thread == nullptr) {
status_bar_update_timer.stop();
@@ -1741,17 +1838,17 @@ void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string det
"data, or other bugs.");
switch (result) {
case Core::System::ResultStatus::ErrorSystemFiles: {
- QString message = "yuzu was unable to locate a Switch system archive";
+ QString message = tr("yuzu was unable to locate a Switch system archive");
if (!details.empty()) {
- message.append(tr(": %1. ").arg(details.c_str()));
+ message.append(tr(": %1. ").arg(QString::fromStdString(details)));
} else {
- message.append(". ");
+ message.append(tr(". "));
}
message.append(common_message);
answer = QMessageBox::question(this, tr("System Archive Not Found"), message,
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
- status_message = "System Archive Missing";
+ status_message = tr("System Archive Missing");
break;
}
@@ -1760,7 +1857,7 @@ void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string det
message.append(common_message);
answer = QMessageBox::question(this, tr("Shared Fonts Not Found"), message,
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
- status_message = "Shared Font Missing";
+ status_message = tr("Shared Font Missing");
break;
}
@@ -1776,7 +1873,7 @@ void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string det
"Continuing emulation may result in crashes, corrupted save data, or other "
"bugs."),
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
- status_message = "Fatal Error encountered";
+ status_message = tr("Fatal Error encountered");
break;
}
@@ -1827,18 +1924,19 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
};
QString errors;
-
- if (!pdm.HasFuses())
+ if (!pdm.HasFuses()) {
errors += tr("- Missing fuses - Cannot derive SBK\n");
- if (!pdm.HasBoot0())
+ }
+ if (!pdm.HasBoot0()) {
errors += tr("- Missing BOOT0 - Cannot derive master keys\n");
- if (!pdm.HasPackage2())
+ }
+ if (!pdm.HasPackage2()) {
errors += tr("- Missing BCPKG2-1-Normal-Main - Cannot derive general keys\n");
- if (!pdm.HasProdInfo())
+ }
+ if (!pdm.HasProdInfo()) {
errors += tr("- Missing PRODINFO - Cannot derive title keys\n");
-
+ }
if (!errors.isEmpty()) {
-
QMessageBox::warning(
this, tr("Warning Missing Derivation Components"),
tr("The following are missing from your configuration that may hinder key "
@@ -1846,7 +1944,7 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
errors +
tr("<br><br>You can get all of these and dump all of your games easily by "
"following <a href='https://yuzu-emu.org/help/quickstart/'>the "
- "quickstart guide</a>. Alternatively, you can use another method of dumping"
+ "quickstart guide</a>. Alternatively, you can use another method of dumping "
"to obtain all of your keys."));
}
@@ -1869,31 +1967,34 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
Service::FileSystem::CreateFactories(*vfs);
if (behavior == ReinitializeKeyBehavior::Warning) {
- game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
+ game_list->PopulateAsync(UISettings::values.game_directory_path,
+ UISettings::values.game_directory_deepscan);
}
}
-std::optional<u64> GMainWindow::SelectRomFSDumpTarget(
- const FileSys::RegisteredCacheUnion& installed, u64 program_id) {
+std::optional<u64> GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed,
+ u64 program_id) {
const auto dlc_entries =
installed.ListEntriesFilter(FileSys::TitleType::AOC, FileSys::ContentRecordType::Data);
- std::vector<FileSys::RegisteredCacheEntry> dlc_match;
+ std::vector<FileSys::ContentProviderEntry> dlc_match;
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::RegisteredCacheEntry& entry) {
+ [&program_id, &installed](const FileSys::ContentProviderEntry& entry) {
return (entry.title_id & DLC_BASE_TITLE_ID_MASK) == program_id &&
installed.GetEntry(entry)->GetStatus() == Loader::ResultStatus::Success;
});
std::vector<u64> romfs_tids;
romfs_tids.push_back(program_id);
- for (const auto& entry : dlc_match)
+ for (const auto& entry : dlc_match) {
romfs_tids.push_back(entry.title_id);
+ }
if (romfs_tids.size() > 1) {
- QStringList list{"Base"};
- for (std::size_t i = 1; i < romfs_tids.size(); ++i)
+ QStringList list{QStringLiteral("Base")};
+ for (std::size_t i = 1; i < romfs_tids.size(); ++i) {
list.push_back(QStringLiteral("DLC %1").arg(romfs_tids[i] & 0x7FF));
+ }
bool ok;
const auto res = QInputDialog::getItem(
@@ -1976,6 +2077,18 @@ void GMainWindow::dragMoveEvent(QDragMoveEvent* event) {
event->acceptProposedAction();
}
+void GMainWindow::keyPressEvent(QKeyEvent* event) {
+ if (render_window) {
+ render_window->ForwardKeyPressEvent(event);
+ }
+}
+
+void GMainWindow::keyReleaseEvent(QKeyEvent* event) {
+ if (render_window) {
+ render_window->ForwardKeyReleaseEvent(event);
+ }
+}
+
bool GMainWindow::ConfirmChangeGame() {
if (emu_thread == nullptr)
return true;
@@ -1993,26 +2106,32 @@ void GMainWindow::filterBarSetChecked(bool state) {
}
void GMainWindow::UpdateUITheme() {
+ const QString default_icons = QStringLiteral(":/icons/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);
- if (UISettings::values.theme != UISettings::themes[0].second &&
- !UISettings::values.theme.isEmpty()) {
- const QString theme_uri(":" + UISettings::values.theme + "/style.qss");
+
+ if (is_default_theme || current_theme.isEmpty()) {
+ qApp->setStyleSheet({});
+ setStyleSheet({});
+ theme_paths.append(default_icons);
+ QIcon::setThemeName(default_icons);
+ } else {
+ const QString theme_uri(QLatin1Char{':'} + current_theme + QStringLiteral("/style.qss"));
QFile f(theme_uri);
if (f.open(QFile::ReadOnly | QFile::Text)) {
QTextStream ts(&f);
qApp->setStyleSheet(ts.readAll());
- GMainWindow::setStyleSheet(ts.readAll());
+ setStyleSheet(ts.readAll());
} else {
LOG_ERROR(Frontend, "Unable to set style, stylesheet file not found");
}
- theme_paths.append(QStringList{":/icons/default", ":/icons/" + UISettings::values.theme});
- QIcon::setThemeName(":/icons/" + UISettings::values.theme);
- } else {
- qApp->setStyleSheet("");
- GMainWindow::setStyleSheet("");
- theme_paths.append(QStringList{":/icons/default"});
- QIcon::setThemeName(":/icons/default");
+
+ const QString theme_name = QStringLiteral(":/icons/") + current_theme;
+ theme_paths.append({default_icons, theme_name});
+ QIcon::setThemeName(theme_name);
}
+
QIcon::setThemeSearchPaths(theme_paths);
emit UpdateThemedIcons();
}
@@ -2040,10 +2159,11 @@ int main(int argc, char* argv[]) {
SCOPE_EXIT({ MicroProfileShutdown(); });
// Init settings params
- QCoreApplication::setOrganizationName("yuzu team");
- QCoreApplication::setApplicationName("yuzu");
+ QCoreApplication::setOrganizationName(QStringLiteral("yuzu team"));
+ QCoreApplication::setApplicationName(QStringLiteral("yuzu"));
- QApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity);
+ // Enables the core to make the qt created contexts current on std::threads
+ QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity);
QApplication app(argc, argv);
// Qt changes the locale and causes issues in float conversion using std::to_string() when
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index e07c892cf..1137bbc7a 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -23,7 +23,6 @@ class EmuThread;
class GameList;
class GImageInfo;
class GraphicsBreakPointsWidget;
-class GraphicsSurfaceWidget;
class GRenderWindow;
class LoadingScreen;
class MicroProfileDialog;
@@ -37,7 +36,8 @@ struct SoftwareKeyboardParameters;
} // namespace Core::Frontend
namespace FileSys {
-class RegisteredCacheUnion;
+class ContentProvider;
+class ManualContentProvider;
class VfsFilesystem;
} // namespace FileSys
@@ -102,7 +102,9 @@ signals:
// Signal that tells widgets to update icons to use the current theme
void UpdateThemedIcons();
- void ProfileSelectorFinishedSelection(std::optional<Service::Account::UUID> uuid);
+ void ErrorDisplayFinished();
+
+ void ProfileSelectorFinishedSelection(std::optional<Common::UUID> uuid);
void SoftwareKeyboardFinishedText(std::optional<std::u16string> text);
void SoftwareKeyboardFinishedCheckDialog();
@@ -111,6 +113,7 @@ signals:
public slots:
void OnLoadComplete();
+ void ErrorDisplayDisplayError(QString body);
void ProfileSelectorSelectProfile();
void SoftwareKeyboardGetText(const Core::Frontend::SoftwareKeyboardParameters& parameters);
void SoftwareKeyboardInvokeCheckDialog(std::u16string error_message);
@@ -120,7 +123,6 @@ private:
void InitializeWidgets();
void InitializeDebugWidgets();
void InitializeRecentFileMenuActions();
- void InitializeHotkeys();
void SetDefaultUIGeometry();
void RestoreUIState();
@@ -176,6 +178,7 @@ private slots:
/// Called whenever a user selects a game in the game list widget.
void OnGameListLoadFile(QString game_path);
void OnGameListOpenFolder(u64 program_id, GameListOpenTarget target);
+ void OnTransferableShaderCacheOpenFile(u64 program_id);
void OnGameListDumpRomFS(u64 program_id, const std::string& game_path);
void OnGameListCopyTID(u64 program_id);
void OnGameListNavigateToGamedbEntry(u64 program_id,
@@ -195,6 +198,7 @@ private slots:
void OnAbout();
void OnToggleFilterBar();
void OnDisplayTitleBars(bool);
+ void InitializeHotkeys();
void ToggleFullscreen();
void ShowFullscreen();
void HideFullscreen();
@@ -204,7 +208,8 @@ private slots:
void OnReinitializeKeys(ReinitializeKeyBehavior behavior);
private:
- std::optional<u64> SelectRomFSDumpTarget(const FileSys::RegisteredCacheUnion&, u64 program_id);
+ std::optional<u64> SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id);
+ void UpdateWindowTitle(const QString& title_name = {});
void UpdateStatusBar();
Ui::MainWindow ui;
@@ -232,12 +237,12 @@ private:
// FS
std::shared_ptr<FileSys::VfsFilesystem> vfs;
+ std::unique_ptr<FileSys::ManualContentProvider> provider;
// Debugger panes
ProfilerWidget* profilerWidget;
MicroProfileDialog* microProfileDialog;
GraphicsBreakPointsWidget* graphicsBreakpointsWidget;
- GraphicsSurfaceWidget* graphicsSurfaceWidget;
WaitTreeWidget* waitTreeWidget;
QAction* actions_recent_files[max_recent_files_item];
@@ -251,4 +256,8 @@ protected:
void dropEvent(QDropEvent* event) override;
void dragEnterEvent(QDragEnterEvent* event) override;
void dragMoveEvent(QDragMoveEvent* event) override;
+
+ // Overrides used to forward signals to the render window when the focus moves out.
+ void keyPressEvent(QKeyEvent* event) override;
+ void keyReleaseEvent(QKeyEvent* event) override;
};
diff --git a/src/yuzu/ui_settings.cpp b/src/yuzu/ui_settings.cpp
index a314493fc..4bdc302e0 100644
--- a/src/yuzu/ui_settings.cpp
+++ b/src/yuzu/ui_settings.cpp
@@ -12,5 +12,4 @@ const Themes themes{{
}};
Values values = {};
-
} // namespace UISettings
diff --git a/src/yuzu/ui_settings.h b/src/yuzu/ui_settings.h
index 82aaeedb0..a62cd6911 100644
--- a/src/yuzu/ui_settings.h
+++ b/src/yuzu/ui_settings.h
@@ -15,7 +15,12 @@
namespace UISettings {
using ContextualShortcut = std::pair<QString, int>;
-using Shortcut = std::pair<QString, ContextualShortcut>;
+
+struct Shortcut {
+ QString name;
+ QString group;
+ ContextualShortcut shortcut;
+};
using Themes = std::array<std::pair<const char*, const char*>, 2>;
extern const Themes themes;
@@ -50,8 +55,8 @@ struct Values {
QString roms_path;
QString symbols_path;
QString screenshot_path;
- QString gamedir;
- bool gamedir_deepscan;
+ QString game_directory_path;
+ bool game_directory_deepscan;
QStringList recent_files;
QString theme;
@@ -74,6 +79,7 @@ struct Values {
uint8_t row_1_text_id;
uint8_t row_2_text_id;
std::atomic_bool is_game_list_reload_pending{false};
+ bool cache_game_list;
};
extern Values values;
diff --git a/src/yuzu/util/sequence_dialog/sequence_dialog.cpp b/src/yuzu/util/sequence_dialog/sequence_dialog.cpp
new file mode 100644
index 000000000..bb5f74ec4
--- /dev/null
+++ b/src/yuzu/util/sequence_dialog/sequence_dialog.cpp
@@ -0,0 +1,40 @@
+// Copyright 2018 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <QDialogButtonBox>
+#include <QKeySequenceEdit>
+#include <QVBoxLayout>
+#include "yuzu/util/sequence_dialog/sequence_dialog.h"
+
+SequenceDialog::SequenceDialog(QWidget* parent) : QDialog(parent) {
+ setWindowTitle(tr("Enter a hotkey"));
+ setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
+
+ key_sequence = new QKeySequenceEdit;
+
+ auto* const buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
+ buttons->setCenterButtons(true);
+
+ auto* const layout = new QVBoxLayout(this);
+ layout->addWidget(key_sequence);
+ layout->addWidget(buttons);
+
+ connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
+ connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
+}
+
+SequenceDialog::~SequenceDialog() = default;
+
+QKeySequence SequenceDialog::GetSequence() const {
+ // Only the first key is returned. The other 3, if present, are ignored.
+ return QKeySequence(key_sequence->keySequence()[0]);
+}
+
+bool SequenceDialog::focusNextPrevChild(bool next) {
+ return false;
+}
+
+void SequenceDialog::closeEvent(QCloseEvent*) {
+ reject();
+}
diff --git a/src/yuzu/util/sequence_dialog/sequence_dialog.h b/src/yuzu/util/sequence_dialog/sequence_dialog.h
new file mode 100644
index 000000000..969c77740
--- /dev/null
+++ b/src/yuzu/util/sequence_dialog/sequence_dialog.h
@@ -0,0 +1,24 @@
+// Copyright 2018 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <QDialog>
+
+class QKeySequenceEdit;
+
+class SequenceDialog : public QDialog {
+ Q_OBJECT
+
+public:
+ explicit SequenceDialog(QWidget* parent = nullptr);
+ ~SequenceDialog() override;
+
+ QKeySequence GetSequence() const;
+ void closeEvent(QCloseEvent*) override;
+
+private:
+ QKeySequenceEdit* key_sequence;
+ bool focusNextPrevChild(bool next) override;
+};
diff --git a/src/yuzu/util/spinbox.cpp b/src/yuzu/util/spinbox.cpp
deleted file mode 100644
index 14ef1e884..000000000
--- a/src/yuzu/util/spinbox.cpp
+++ /dev/null
@@ -1,278 +0,0 @@
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-// Copyright 2014 Tony Wasserka
-// All rights reserved.
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are met:
-//
-// * Redistributions of source code must retain the above copyright
-// notice, this list of conditions and the following disclaimer.
-// * 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.
-// * Neither the name of the owner nor the names of its contributors may
-// be used to endorse or promote products derived from this software
-// without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-// "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 COPYRIGHT
-// OWNER OR CONTRIBUTORS 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.
-
-#include <cstdlib>
-#include <QLineEdit>
-#include <QRegExpValidator>
-#include "common/assert.h"
-#include "yuzu/util/spinbox.h"
-
-CSpinBox::CSpinBox(QWidget* parent)
- : QAbstractSpinBox(parent), min_value(-100), max_value(100), value(0), base(10), num_digits(0) {
- // TODO: Might be nice to not immediately call the slot.
- // Think of an address that is being replaced by a different one, in which case a lot
- // invalid intermediate addresses would be read from during editing.
- connect(lineEdit(), &QLineEdit::textEdited, this, &CSpinBox::OnEditingFinished);
-
- UpdateText();
-}
-
-void CSpinBox::SetValue(qint64 val) {
- auto old_value = value;
- value = std::max(std::min(val, max_value), min_value);
-
- if (old_value != value) {
- UpdateText();
- emit ValueChanged(value);
- }
-}
-
-void CSpinBox::SetRange(qint64 min, qint64 max) {
- min_value = min;
- max_value = max;
-
- SetValue(value);
- UpdateText();
-}
-
-void CSpinBox::stepBy(int steps) {
- auto new_value = value;
- // Scale number of steps by the currently selected digit
- // TODO: Move this code elsewhere and enable it.
- // TODO: Support for num_digits==0, too
- // TODO: Support base!=16, too
- // TODO: Make the cursor not jump back to the end of the line...
- /*if (base == 16 && num_digits > 0) {
- int digit = num_digits - (lineEdit()->cursorPosition() - prefix.length()) - 1;
- digit = std::max(0, std::min(digit, num_digits - 1));
- steps <<= digit * 4;
- }*/
-
- // Increment "new_value" by "steps", and perform annoying overflow checks, too.
- if (steps < 0 && new_value + steps > new_value) {
- new_value = std::numeric_limits<qint64>::min();
- } else if (steps > 0 && new_value + steps < new_value) {
- new_value = std::numeric_limits<qint64>::max();
- } else {
- new_value += steps;
- }
-
- SetValue(new_value);
- UpdateText();
-}
-
-QAbstractSpinBox::StepEnabled CSpinBox::stepEnabled() const {
- StepEnabled ret = StepNone;
-
- if (value > min_value)
- ret |= StepDownEnabled;
-
- if (value < max_value)
- ret |= StepUpEnabled;
-
- return ret;
-}
-
-void CSpinBox::SetBase(int base) {
- this->base = base;
-
- UpdateText();
-}
-
-void CSpinBox::SetNumDigits(int num_digits) {
- this->num_digits = num_digits;
-
- UpdateText();
-}
-
-void CSpinBox::SetPrefix(const QString& prefix) {
- this->prefix = prefix;
-
- UpdateText();
-}
-
-void CSpinBox::SetSuffix(const QString& suffix) {
- this->suffix = suffix;
-
- UpdateText();
-}
-
-static QString StringToInputMask(const QString& input) {
- QString mask = input;
-
- // ... replace any special characters by their escaped counterparts ...
- mask.replace("\\", "\\\\");
- mask.replace("A", "\\A");
- mask.replace("a", "\\a");
- mask.replace("N", "\\N");
- mask.replace("n", "\\n");
- mask.replace("X", "\\X");
- mask.replace("x", "\\x");
- mask.replace("9", "\\9");
- mask.replace("0", "\\0");
- mask.replace("D", "\\D");
- mask.replace("d", "\\d");
- mask.replace("#", "\\#");
- mask.replace("H", "\\H");
- mask.replace("h", "\\h");
- mask.replace("B", "\\B");
- mask.replace("b", "\\b");
- mask.replace(">", "\\>");
- mask.replace("<", "\\<");
- mask.replace("!", "\\!");
-
- return mask;
-}
-
-void CSpinBox::UpdateText() {
- // If a fixed number of digits is used, we put the line edit in insertion mode by setting an
- // input mask.
- QString mask;
- if (num_digits != 0) {
- mask += StringToInputMask(prefix);
-
- // For base 10 and negative range, demand a single sign character
- if (HasSign())
- mask += "X"; // identified as "-" or "+" in the validator
-
- // Uppercase digits greater than 9.
- mask += ">";
-
- // Match num_digits digits
- // Digits irrelevant to the chosen number base are filtered in the validator
- mask += QString("H").repeated(std::max(num_digits, 1));
-
- // Switch off case conversion
- mask += "!";
-
- mask += StringToInputMask(suffix);
- }
- lineEdit()->setInputMask(mask);
-
- // Set new text without changing the cursor position. This will cause the cursor to briefly
- // appear at the end of the line and then to jump back to its original position. That's
- // a bit ugly, but better than having setText() move the cursor permanently all the time.
- int cursor_position = lineEdit()->cursorPosition();
- lineEdit()->setText(TextFromValue());
- lineEdit()->setCursorPosition(cursor_position);
-}
-
-QString CSpinBox::TextFromValue() {
- return prefix + QString(HasSign() ? ((value < 0) ? "-" : "+") : "") +
- QString("%1").arg(std::abs(value), num_digits, base, QLatin1Char('0')).toUpper() +
- suffix;
-}
-
-qint64 CSpinBox::ValueFromText() {
- unsigned strpos = prefix.length();
-
- QString num_string = text().mid(strpos, text().length() - strpos - suffix.length());
- return num_string.toLongLong(nullptr, base);
-}
-
-bool CSpinBox::HasSign() const {
- return base == 10 && min_value < 0;
-}
-
-void CSpinBox::OnEditingFinished() {
- // Only update for valid input
- QString input = lineEdit()->text();
- int pos = 0;
- if (QValidator::Acceptable == validate(input, pos))
- SetValue(ValueFromText());
-}
-
-QValidator::State CSpinBox::validate(QString& input, int& pos) const {
- if (!prefix.isEmpty() && input.left(prefix.length()) != prefix)
- return QValidator::Invalid;
-
- int strpos = prefix.length();
-
- // Empty "numbers" allowed as intermediate values
- if (strpos >= input.length() - HasSign() - suffix.length())
- return QValidator::Intermediate;
-
- DEBUG_ASSERT(base <= 10 || base == 16);
- QString regexp;
-
- // Demand sign character for negative ranges
- if (HasSign())
- regexp += "[+\\-]";
-
- // Match digits corresponding to the chosen number base.
- regexp += QString("[0-%1").arg(std::min(base, 9));
- if (base == 16) {
- regexp += "a-fA-F";
- }
- regexp += "]";
-
- // Specify number of digits
- if (num_digits > 0) {
- regexp += QString("{%1}").arg(num_digits);
- } else {
- regexp += "+";
- }
-
- // Match string
- QRegExp num_regexp(regexp);
- int num_pos = strpos;
- QString sub_input = input.mid(strpos, input.length() - strpos - suffix.length());
-
- if (!num_regexp.exactMatch(sub_input) && num_regexp.matchedLength() == 0)
- return QValidator::Invalid;
-
- sub_input = sub_input.left(num_regexp.matchedLength());
- bool ok;
- qint64 val = sub_input.toLongLong(&ok, base);
-
- if (!ok)
- return QValidator::Invalid;
-
- // Outside boundaries => don't accept
- if (val < min_value || val > max_value)
- return QValidator::Invalid;
-
- // Make sure we are actually at the end of this string...
- strpos += num_regexp.matchedLength();
-
- if (!suffix.isEmpty() && input.mid(strpos) != suffix) {
- return QValidator::Invalid;
- } else {
- strpos += suffix.length();
- }
-
- if (strpos != input.length())
- return QValidator::Invalid;
-
- // At this point we can say for sure that the input is fine. Let's fix it up a bit though
- input.replace(num_pos, sub_input.length(), sub_input.toUpper());
-
- return QValidator::Acceptable;
-}
diff --git a/src/yuzu/util/spinbox.h b/src/yuzu/util/spinbox.h
deleted file mode 100644
index 2fa1db3a4..000000000
--- a/src/yuzu/util/spinbox.h
+++ /dev/null
@@ -1,86 +0,0 @@
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-// Copyright 2014 Tony Wasserka
-// All rights reserved.
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are met:
-//
-// * Redistributions of source code must retain the above copyright
-// notice, this list of conditions and the following disclaimer.
-// * 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.
-// * Neither the name of the owner nor the names of its contributors may
-// be used to endorse or promote products derived from this software
-// without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-// "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 COPYRIGHT
-// OWNER OR CONTRIBUTORS 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
-
-#include <QAbstractSpinBox>
-#include <QtGlobal>
-
-class QVariant;
-
-/**
- * A custom spin box widget with enhanced functionality over Qt's QSpinBox
- */
-class CSpinBox : public QAbstractSpinBox {
- Q_OBJECT
-
-public:
- explicit CSpinBox(QWidget* parent = nullptr);
-
- void stepBy(int steps) override;
- StepEnabled stepEnabled() const override;
-
- void SetValue(qint64 val);
-
- void SetRange(qint64 min, qint64 max);
-
- void SetBase(int base);
-
- void SetPrefix(const QString& prefix);
- void SetSuffix(const QString& suffix);
-
- void SetNumDigits(int num_digits);
-
- QValidator::State validate(QString& input, int& pos) const override;
-
-signals:
- void ValueChanged(qint64 val);
-
-private slots:
- void OnEditingFinished();
-
-private:
- void UpdateText();
-
- bool HasSign() const;
-
- QString TextFromValue();
- qint64 ValueFromText();
-
- qint64 min_value, max_value;
-
- qint64 value;
-
- QString prefix, suffix;
-
- int base;
-
- int num_digits;
-};
diff --git a/src/yuzu/util/util.cpp b/src/yuzu/util/util.cpp
index 62c080aff..ef31bc2d2 100644
--- a/src/yuzu/util/util.cpp
+++ b/src/yuzu/util/util.cpp
@@ -8,7 +8,7 @@
#include "yuzu/util/util.h"
QFont GetMonospaceFont() {
- QFont font("monospace");
+ QFont font(QStringLiteral("monospace"));
// Automatic fallback to a monospace font on on platforms without a font called "monospace"
font.setStyleHint(QFont::Monospace);
font.setFixedPitch(true);
@@ -16,14 +16,16 @@ QFont GetMonospaceFont() {
}
QString ReadableByteSize(qulonglong size) {
- static const std::array<const char*, 6> units = {"B", "KiB", "MiB", "GiB", "TiB", "PiB"};
- if (size == 0)
- return "0";
- int digit_groups = std::min<int>(static_cast<int>(std::log10(size) / std::log10(1024)),
- static_cast<int>(units.size()));
- return QString("%L1 %2")
+ static constexpr std::array units{"B", "KiB", "MiB", "GiB", "TiB", "PiB"};
+ if (size == 0) {
+ return QStringLiteral("0");
+ }
+
+ const int digit_groups = std::min(static_cast<int>(std::log10(size) / std::log10(1024)),
+ static_cast<int>(units.size()));
+ return QStringLiteral("%L1 %2")
.arg(size / std::pow(1024, digit_groups), 0, 'f', 1)
- .arg(units[digit_groups]);
+ .arg(QString::fromUtf8(units[digit_groups]));
}
QPixmap CreateCirclePixmapFromColor(const QColor& color) {
diff --git a/src/yuzu_cmd/CMakeLists.txt b/src/yuzu_cmd/CMakeLists.txt
index 297dab653..b5f06ab9e 100644
--- a/src/yuzu_cmd/CMakeLists.txt
+++ b/src/yuzu_cmd/CMakeLists.txt
@@ -4,6 +4,8 @@ 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
resource.h
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp
index ca880dc65..f3817bb87 100644
--- a/src/yuzu_cmd/config.cpp
+++ b/src/yuzu_cmd/config.cpp
@@ -26,12 +26,12 @@ Config::Config() {
Config::~Config() = default;
bool Config::LoadINI(const std::string& default_contents, bool retry) {
- const char* location = this->sdl2_config_loc.c_str();
+ const std::string& location = this->sdl2_config_loc;
if (sdl2_config->ParseError() < 0) {
if (retry) {
LOG_WARNING(Config, "Failed to load {}. Creating file from defaults...", location);
FileUtil::CreateFullPath(location);
- FileUtil::WriteStringToFile(true, default_contents, location);
+ FileUtil::WriteStringToFile(true, location, default_contents);
sdl2_config = std::make_unique<INIReader>(location); // Reopen file
return LoadINI(default_contents, false);
@@ -319,7 +319,6 @@ void Config::ReadValues() {
// System
Settings::values.use_docked_mode = sdl2_config->GetBoolean("System", "use_docked_mode", false);
- Settings::values.enable_nfc = sdl2_config->GetBoolean("System", "enable_nfc", true);
const auto size = sdl2_config->GetInteger("System", "users_size", 0);
Settings::values.current_user = std::clamp<int>(
@@ -346,7 +345,7 @@ void Config::ReadValues() {
// Renderer
Settings::values.resolution_factor =
- (float)sdl2_config->GetReal("Renderer", "resolution_factor", 1.0);
+ static_cast<float>(sdl2_config->GetReal("Renderer", "resolution_factor", 1.0));
Settings::values.use_frame_limit = sdl2_config->GetBoolean("Renderer", "use_frame_limit", true);
Settings::values.frame_limit =
static_cast<u16>(sdl2_config->GetInteger("Renderer", "frame_limit", 100));
@@ -357,16 +356,17 @@ void Config::ReadValues() {
Settings::values.use_asynchronous_gpu_emulation =
sdl2_config->GetBoolean("Renderer", "use_asynchronous_gpu_emulation", false);
- Settings::values.bg_red = (float)sdl2_config->GetReal("Renderer", "bg_red", 0.0);
- Settings::values.bg_green = (float)sdl2_config->GetReal("Renderer", "bg_green", 0.0);
- Settings::values.bg_blue = (float)sdl2_config->GetReal("Renderer", "bg_blue", 0.0);
+ Settings::values.bg_red = static_cast<float>(sdl2_config->GetReal("Renderer", "bg_red", 0.0));
+ Settings::values.bg_green =
+ static_cast<float>(sdl2_config->GetReal("Renderer", "bg_green", 0.0));
+ Settings::values.bg_blue = static_cast<float>(sdl2_config->GetReal("Renderer", "bg_blue", 0.0));
// Audio
Settings::values.sink_id = sdl2_config->Get("Audio", "output_engine", "auto");
Settings::values.enable_audio_stretching =
sdl2_config->GetBoolean("Audio", "enable_audio_stretching", true);
Settings::values.audio_device_id = sdl2_config->Get("Audio", "output_device", "auto");
- Settings::values.volume = sdl2_config->GetReal("Audio", "volume", 1);
+ Settings::values.volume = static_cast<float>(sdl2_config->GetReal("Audio", "volume", 1));
Settings::values.language_index = sdl2_config->GetInteger("System", "language_index", 1);
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
index 7df8eff53..a6edc089a 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
@@ -2,23 +2,28 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include <algorithm>
-#include <cstdlib>
-#include <string>
-#define SDL_MAIN_HANDLED
#include <SDL.h>
-#include <fmt/format.h>
-#include <glad/glad.h>
#include "common/logging/log.h"
-#include "common/scm_rev.h"
-#include "common/string_util.h"
-#include "core/settings.h"
#include "input_common/keyboard.h"
#include "input_common/main.h"
#include "input_common/motion_emu.h"
#include "input_common/sdl/sdl.h"
#include "yuzu_cmd/emu_window/emu_window_sdl2.h"
+EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) {
+ if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) < 0) {
+ LOG_CRITICAL(Frontend, "Failed to initialize SDL2! Exiting...");
+ exit(1);
+ }
+ InputCommon::Init();
+ SDL_SetMainReady();
+}
+
+EmuWindow_SDL2::~EmuWindow_SDL2() {
+ InputCommon::Shutdown();
+ SDL_Quit();
+}
+
void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) {
TouchMoved((unsigned)std::max(x, 0), (unsigned)std::max(y, 0));
InputCommon::GetMotionEmu()->Tilt(x, y);
@@ -108,110 +113,6 @@ void EmuWindow_SDL2::Fullscreen() {
SDL_MaximizeWindow(render_window);
}
-bool EmuWindow_SDL2::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::EmuWindow_SDL2(bool fullscreen) {
- InputCommon::Init();
-
- SDL_SetMainReady();
-
- // Initialize the window
- if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) < 0) {
- LOG_CRITICAL(Frontend, "Failed to initialize SDL2! Exiting...");
- exit(1);
- }
-
- 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 {} | {}-{}", 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);
-
- if (render_window == nullptr) {
- LOG_CRITICAL(Frontend, "Failed to create SDL2 window! {}", SDL_GetError());
- exit(1);
- }
-
- if (fullscreen) {
- Fullscreen();
- }
-
- 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);
- }
-
- OnResize();
- OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
- SDL_PumpEvents();
- SDL_GL_SetSwapInterval(false);
- LOG_INFO(Frontend, "yuzu Version: {} | {}-{}", Common::g_build_fullname, Common::g_scm_branch,
- Common::g_scm_desc);
- Settings::LogSettings();
-
- DoneCurrent();
-}
-
-EmuWindow_SDL2::~EmuWindow_SDL2() {
- InputCommon::SDL::CloseSDLJoysticks();
- SDL_GL_DeleteContext(gl_context);
- SDL_Quit();
-
- InputCommon::Shutdown();
-}
-
-void EmuWindow_SDL2::SwapBuffers() {
- SDL_GL_SwapWindow(render_window);
-}
-
void EmuWindow_SDL2::PollEvents() {
SDL_Event event;
@@ -224,7 +125,11 @@ void EmuWindow_SDL2::PollEvents() {
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:
@@ -262,22 +167,11 @@ void EmuWindow_SDL2::PollEvents() {
is_open = false;
break;
default:
- InputCommon::SDL::HandleGameControllerEvent(event);
break;
}
}
}
-void EmuWindow_SDL2::MakeCurrent() {
- SDL_GL_MakeCurrent(render_window, gl_context);
-}
-
-void EmuWindow_SDL2::DoneCurrent() {
- SDL_GL_MakeCurrent(render_window, nullptr);
-}
-
-void EmuWindow_SDL2::OnMinimalClientAreaChangeRequest(
- const std::pair<unsigned, unsigned>& minimal_size) {
-
+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 b0d4116cc..d8051ebdf 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2.h
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.h
@@ -15,22 +15,13 @@ public:
explicit EmuWindow_SDL2(bool fullscreen);
~EmuWindow_SDL2();
- /// Swap buffers to display the next frame
- void SwapBuffers() override;
-
/// Polls window events
void PollEvents() override;
- /// Makes the graphics context current for the caller thread
- void MakeCurrent() override;
-
- /// Releases the GL context from the caller thread
- void DoneCurrent() override;
-
/// Whether the window is still open, and a close request hasn't yet been sent
bool IsOpen() const;
-private:
+protected:
/// Called by PollEvents when a key is pressed or released.
void OnKeyEvent(int key, u8 state);
@@ -58,20 +49,15 @@ private:
/// Called when user passes the fullscreen parameter flag
void Fullscreen();
- /// Whether the GPU and driver supports the OpenGL extension required
- bool SupportsRequiredGLExtensions();
-
/// Called when a configuration change affects the minimal size of the window
- void OnMinimalClientAreaChangeRequest(
- const std::pair<unsigned, unsigned>& minimal_size) override;
+ void OnMinimalClientAreaChangeRequest(std::pair<unsigned, unsigned> minimal_size) override;
/// Is the window still open?
bool is_open = true;
+ /// Is the window being shown?
+ bool is_shown = true;
+
/// 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_cmd/emu_window/emu_window_sdl2_gl.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp
new file mode 100644
index 000000000..e2d3df180
--- /dev/null
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp
@@ -0,0 +1,150 @@
+// 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>
+#define SDL_MAIN_HANDLED
+#include <SDL.h>
+#include <fmt/format.h>
+#include <glad/glad.h>
+#include "common/logging/log.h"
+#include "common/scm_rev.h"
+#include "common/string_util.h"
+#include "core/settings.h"
+#include "input_common/keyboard.h"
+#include "input_common/main.h"
+#include "input_common/motion_emu.h"
+#include "yuzu_cmd/emu_window/emu_window_sdl2_gl.h"
+
+class SDLGLContext : public Core::Frontend::GraphicsContext {
+public:
+ explicit SDLGLContext() {
+ // create a hidden window to make the shared context against
+ window = SDL_CreateWindow("", SDL_WINDOWPOS_UNDEFINED, // x position
+ SDL_WINDOWPOS_UNDEFINED, // y position
+ Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height,
+ SDL_WINDOW_OPENGL | SDL_WINDOW_HIDDEN);
+ context = SDL_GL_CreateContext(window);
+ }
+
+ ~SDLGLContext() {
+ SDL_GL_DeleteContext(context);
+ SDL_DestroyWindow(window);
+ }
+
+ void MakeCurrent() override {
+ SDL_GL_MakeCurrent(window, context);
+ }
+
+ void DoneCurrent() override {
+ SDL_GL_MakeCurrent(window, nullptr);
+ }
+
+ void SwapBuffers() override {}
+
+private:
+ SDL_Window* window;
+ SDL_GLContext context;
+};
+
+bool EmuWindow_SDL2_GL::SupportsRequiredGLExtensions() {
+ std::vector<std::string> unsupported_ext;
+
+ 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_GL::EmuWindow_SDL2_GL(bool fullscreen) : EmuWindow_SDL2(fullscreen) {
+ 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_COMPATIBILITY);
+ 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);
+ SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1);
+
+ std::string window_title = fmt::format("yuzu {} | {}-{}", 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);
+
+ if (render_window == nullptr) {
+ LOG_CRITICAL(Frontend, "Failed to create SDL2 window! {}", SDL_GetError());
+ exit(1);
+ }
+
+ if (fullscreen) {
+ Fullscreen();
+ }
+ 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);
+ }
+
+ OnResize();
+ OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
+ SDL_PumpEvents();
+ SDL_GL_SetSwapInterval(false);
+ LOG_INFO(Frontend, "yuzu Version: {} | {}-{}", Common::g_build_fullname, Common::g_scm_branch,
+ Common::g_scm_desc);
+ Settings::LogSettings();
+
+ DoneCurrent();
+}
+
+EmuWindow_SDL2_GL::~EmuWindow_SDL2_GL() {
+ SDL_GL_DeleteContext(gl_context);
+}
+
+void EmuWindow_SDL2_GL::SwapBuffers() {
+ SDL_GL_SwapWindow(render_window);
+}
+
+void EmuWindow_SDL2_GL::MakeCurrent() {
+ SDL_GL_MakeCurrent(render_window, gl_context);
+}
+
+void EmuWindow_SDL2_GL::DoneCurrent() {
+ SDL_GL_MakeCurrent(render_window, nullptr);
+}
+
+std::unique_ptr<Core::Frontend::GraphicsContext> EmuWindow_SDL2_GL::CreateSharedContext() const {
+ return std::make_unique<SDLGLContext>();
+}
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h
new file mode 100644
index 000000000..630deba93
--- /dev/null
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h
@@ -0,0 +1,34 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include "core/frontend/emu_window.h"
+#include "yuzu_cmd/emu_window/emu_window_sdl2.h"
+
+class EmuWindow_SDL2_GL final : public EmuWindow_SDL2 {
+public:
+ explicit EmuWindow_SDL2_GL(bool fullscreen);
+ ~EmuWindow_SDL2_GL();
+
+ /// Swap buffers to display the next frame
+ void SwapBuffers() override;
+
+ /// Makes the graphics context current for the caller thread
+ void MakeCurrent() override;
+
+ /// Releases the GL context from the caller thread
+ void DoneCurrent() override;
+
+ std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override;
+
+private:
+ /// Whether the GPU and driver supports the OpenGL extension required
+ bool SupportsRequiredGLExtensions();
+
+ using SDL_GLContext = void*;
+ /// The OpenGL context associated with the window
+ SDL_GLContext gl_context;
+};
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp
index c6c66a787..129d8ca73 100644
--- a/src/yuzu_cmd/yuzu.cpp
+++ b/src/yuzu_cmd/yuzu.cpp
@@ -31,11 +31,9 @@
#include "video_core/renderer_base.h"
#include "yuzu_cmd/config.h"
#include "yuzu_cmd/emu_window/emu_window_sdl2.h"
+#include "yuzu_cmd/emu_window/emu_window_sdl2_gl.h"
-#include <getopt.h>
-#ifndef _MSC_VER
-#include <unistd.h>
-#endif
+#include "core/file_sys/registered_cache.h"
#ifdef _WIN32
// windows.h needs to be included before shellapi.h
@@ -44,6 +42,12 @@
#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
@@ -114,9 +118,9 @@ int main(int argc, char** argv) {
};
while (optind < argc) {
- char arg = getopt_long(argc, argv, "g:fhvp::", long_options, &option_index);
+ int arg = getopt_long(argc, argv, "g:fhvp::", long_options, &option_index);
if (arg != -1) {
- switch (arg) {
+ switch (static_cast<char>(arg)) {
case 'g':
errno = 0;
gdb_port = strtoul(optarg, &endarg, 0);
@@ -170,7 +174,7 @@ int main(int argc, char** argv) {
Settings::values.use_gdbstub = use_gdbstub;
Settings::Apply();
- std::unique_ptr<EmuWindow_SDL2> emu_window{std::make_unique<EmuWindow_SDL2>(fullscreen)};
+ std::unique_ptr<EmuWindow_SDL2> emu_window{std::make_unique<EmuWindow_SDL2_GL>(fullscreen)};
if (!Settings::values.use_multi_core) {
// Single core mode must acquire OpenGL context for entire emulation session
@@ -178,6 +182,7 @@ int main(int argc, char** argv) {
}
Core::System& system{Core::System::GetInstance()};
+ system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>());
Service::FileSystem::CreateFactories(*system.GetFilesystem());
@@ -187,7 +192,7 @@ int main(int argc, char** argv) {
switch (load_result) {
case Core::System::ResultStatus::ErrorGetLoader:
- LOG_CRITICAL(Frontend, "Failed to obtain loader for %s!", filepath.c_str());
+ LOG_CRITICAL(Frontend, "Failed to obtain loader for {}!", filepath);
return -1;
case Core::System::ResultStatus::ErrorLoader:
LOG_CRITICAL(Frontend, "Failed to load ROM!");
@@ -195,9 +200,6 @@ int main(int argc, char** argv) {
case Core::System::ResultStatus::ErrorNotInitialized:
LOG_CRITICAL(Frontend, "CPUCore not initialized");
return -1;
- case Core::System::ResultStatus::ErrorSystemMode:
- LOG_CRITICAL(Frontend, "Failed to determine system mode!");
- return -1;
case Core::System::ResultStatus::ErrorVideoCore:
LOG_CRITICAL(Frontend, "Failed to initialize VideoCore!");
return -1;
@@ -218,6 +220,7 @@ int main(int argc, char** argv) {
system.TelemetrySession().AddField(Telemetry::FieldType::App, "Frontend", "SDL");
+ emu_window->MakeCurrent();
system.Renderer().Rasterizer().LoadDiskResources();
while (emu_window->IsOpen()) {