summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/common/CMakeLists.txt9
-rw-r--r--src/common/assert.h5
-rw-r--r--src/common/bit_field.h17
-rw-r--r--src/common/chunk_file.h623
-rw-r--r--src/common/cityhash.cpp340
-rw-r--r--src/common/cityhash.h110
-rw-r--r--src/common/code_block.h85
-rw-r--r--src/common/common_funcs.h7
-rw-r--r--src/common/common_types.h30
-rw-r--r--src/common/file_util.cpp108
-rw-r--r--src/common/file_util.h24
-rw-r--r--src/common/hash.cpp141
-rw-r--r--src/common/hash.h55
-rw-r--r--src/common/linear_disk_cache.h167
-rw-r--r--src/common/logging/backend.cpp42
-rw-r--r--src/common/logging/backend.h7
-rw-r--r--src/common/logging/filter.cpp8
-rw-r--r--src/common/logging/filter.h2
-rw-r--r--src/common/logging/log.h68
-rw-r--r--src/common/logging/text_formatter.cpp41
-rw-r--r--src/common/logging/text_formatter.h14
-rw-r--r--src/common/math_util.h5
-rw-r--r--src/common/memory_util.cpp19
-rw-r--r--src/common/param_package.cpp12
-rw-r--r--src/common/platform.h34
-rw-r--r--src/common/string_util.cpp105
-rw-r--r--src/common/string_util.h29
-rw-r--r--src/common/swap.h14
-rw-r--r--src/common/telemetry.h2
-rw-r--r--src/common/thread.h19
-rw-r--r--src/common/timer.cpp13
-rw-r--r--src/common/vector_math.h48
-rw-r--r--src/common/x64/cpu_detect.cpp2
-rw-r--r--src/core/CMakeLists.txt67
-rw-r--r--src/core/arm/arm_interface.h35
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic.cpp42
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic.h7
-rw-r--r--src/core/arm/unicorn/arm_unicorn.cpp55
-rw-r--r--src/core/arm/unicorn/arm_unicorn.h9
-rw-r--r--src/core/core.cpp172
-rw-r--r--src/core/core.h89
-rw-r--r--src/core/core_cpu.cpp119
-rw-r--r--src/core/core_cpu.h78
-rw-r--r--src/core/core_timing.cpp54
-rw-r--r--src/core/core_timing.h55
-rw-r--r--src/core/file_sys/directory.h37
-rw-r--r--src/core/file_sys/disk_filesystem.cpp237
-rw-r--r--src/core/file_sys/disk_filesystem.h84
-rw-r--r--src/core/file_sys/errors.h25
-rw-r--r--src/core/file_sys/filesystem.cpp6
-rw-r--r--src/core/file_sys/filesystem.h39
-rw-r--r--src/core/file_sys/partition_filesystem.cpp124
-rw-r--r--src/core/file_sys/partition_filesystem.h87
-rw-r--r--src/core/file_sys/program_metadata.cpp39
-rw-r--r--src/core/file_sys/romfs_factory.cpp8
-rw-r--r--src/core/file_sys/romfs_factory.h2
-rw-r--r--src/core/file_sys/romfs_filesystem.cpp56
-rw-r--r--src/core/file_sys/romfs_filesystem.h21
-rw-r--r--src/core/file_sys/savedata_factory.cpp54
-rw-r--r--src/core/file_sys/savedata_factory.h33
-rw-r--r--src/core/file_sys/sdmc_factory.cpp39
-rw-r--r--src/core/file_sys/sdmc_factory.h31
-rw-r--r--src/core/frontend/input.h6
-rw-r--r--src/core/gdbstub/gdbstub.cpp241
-rw-r--r--src/core/gdbstub/gdbstub.h9
-rw-r--r--src/core/hle/ipc.h6
-rw-r--r--src/core/hle/ipc_helpers.h7
-rw-r--r--src/core/hle/kernel/condition_variable.cpp64
-rw-r--r--src/core/hle/kernel/condition_variable.h63
-rw-r--r--src/core/hle/kernel/errors.h3
-rw-r--r--src/core/hle/kernel/handle_table.cpp7
-rw-r--r--src/core/hle/kernel/hle_ipc.cpp101
-rw-r--r--src/core/hle/kernel/hle_ipc.h56
-rw-r--r--src/core/hle/kernel/kernel.cpp1
-rw-r--r--src/core/hle/kernel/kernel.h8
-rw-r--r--src/core/hle/kernel/mutex.cpp178
-rw-r--r--src/core/hle/kernel/mutex.h88
-rw-r--r--src/core/hle/kernel/object_address_table.cpp4
-rw-r--r--src/core/hle/kernel/process.cpp37
-rw-r--r--src/core/hle/kernel/process.h8
-rw-r--r--src/core/hle/kernel/resource_limit.cpp48
-rw-r--r--src/core/hle/kernel/resource_limit.h26
-rw-r--r--src/core/hle/kernel/scheduler.cpp28
-rw-r--r--src/core/hle/kernel/scheduler.h3
-rw-r--r--src/core/hle/kernel/server_session.cpp11
-rw-r--r--src/core/hle/kernel/shared_memory.cpp33
-rw-r--r--src/core/hle/kernel/svc.cpp531
-rw-r--r--src/core/hle/kernel/svc.h3
-rw-r--r--src/core/hle/kernel/svc_wrap.h40
-rw-r--r--src/core/hle/kernel/thread.cpp224
-rw-r--r--src/core/hle/kernel/thread.h48
-rw-r--r--src/core/hle/kernel/timer.cpp9
-rw-r--r--src/core/hle/kernel/vm_manager.cpp87
-rw-r--r--src/core/hle/kernel/vm_manager.h40
-rw-r--r--src/core/hle/kernel/wait_object.cpp3
-rw-r--r--src/core/hle/result.h63
-rw-r--r--src/core/hle/service/acc/acc.cpp144
-rw-r--r--src/core/hle/service/acc/acc.h25
-rw-r--r--src/core/hle/service/acc/acc_aa.cpp20
-rw-r--r--src/core/hle/service/acc/acc_aa.h16
-rw-r--r--src/core/hle/service/acc/acc_su.cpp53
-rw-r--r--src/core/hle/service/acc/acc_su.h18
-rw-r--r--src/core/hle/service/acc/acc_u0.cpp119
-rw-r--r--src/core/hle/service/acc/acc_u0.h34
-rw-r--r--src/core/hle/service/acc/acc_u1.cpp40
-rw-r--r--src/core/hle/service/acc/acc_u1.h16
-rw-r--r--src/core/hle/service/am/am.cpp507
-rw-r--r--src/core/hle/service/am/am.h47
-rw-r--r--src/core/hle/service/am/applet_ae.cpp143
-rw-r--r--src/core/hle/service/am/applet_ae.h2
-rw-r--r--src/core/hle/service/am/applet_oe.cpp27
-rw-r--r--src/core/hle/service/aoc/aoc_u.cpp11
-rw-r--r--src/core/hle/service/aoc/aoc_u.h6
-rw-r--r--src/core/hle/service/apm/apm.cpp6
-rw-r--r--src/core/hle/service/apm/apm.h6
-rw-r--r--src/core/hle/service/apm/interface.cpp12
-rw-r--r--src/core/hle/service/apm/interface.h6
-rw-r--r--src/core/hle/service/audio/audin_u.cpp33
-rw-r--r--src/core/hle/service/audio/audin_u.h6
-rw-r--r--src/core/hle/service/audio/audio.cpp6
-rw-r--r--src/core/hle/service/audio/audio.h6
-rw-r--r--src/core/hle/service/audio/audout_u.cpp66
-rw-r--r--src/core/hle/service/audio/audout_u.h6
-rw-r--r--src/core/hle/service/audio/audrec_u.cpp25
-rw-r--r--src/core/hle/service/audio/audrec_u.h6
-rw-r--r--src/core/hle/service/audio/audren_u.cpp220
-rw-r--r--src/core/hle/service/audio/audren_u.h33
-rw-r--r--src/core/hle/service/audio/codecctl.cpp32
-rw-r--r--src/core/hle/service/audio/codecctl.h6
-rw-r--r--src/core/hle/service/bcat/bcat.cpp16
-rw-r--r--src/core/hle/service/bcat/bcat.h16
-rw-r--r--src/core/hle/service/bcat/module.cpp53
-rw-r--r--src/core/hle/service/bcat/module.h27
-rw-r--r--src/core/hle/service/fatal/fatal.cpp36
-rw-r--r--src/core/hle/service/fatal/fatal.h27
-rw-r--r--src/core/hle/service/fatal/fatal_p.cpp12
-rw-r--r--src/core/hle/service/fatal/fatal_p.h16
-rw-r--r--src/core/hle/service/fatal/fatal_u.cpp18
-rw-r--r--src/core/hle/service/fatal/fatal_u.h16
-rw-r--r--src/core/hle/service/filesystem/filesystem.cpp41
-rw-r--r--src/core/hle/service/filesystem/filesystem.h9
-rw-r--r--src/core/hle/service/filesystem/fsp_srv.cpp466
-rw-r--r--src/core/hle/service/filesystem/fsp_srv.h10
-rw-r--r--src/core/hle/service/friend/friend.cpp12
-rw-r--r--src/core/hle/service/friend/friend.h8
-rw-r--r--src/core/hle/service/friend/friend_a.cpp9
-rw-r--r--src/core/hle/service/friend/friend_a.h6
-rw-r--r--src/core/hle/service/friend/friend_u.cpp18
-rw-r--r--src/core/hle/service/friend/friend_u.h16
-rw-r--r--src/core/hle/service/hid/hid.cpp375
-rw-r--r--src/core/hle/service/hid/hid.h104
-rw-r--r--src/core/hle/service/lm/lm.cpp18
-rw-r--r--src/core/hle/service/lm/lm.h6
-rw-r--r--src/core/hle/service/mm/mm_u.cpp50
-rw-r--r--src/core/hle/service/mm/mm_u.h29
-rw-r--r--src/core/hle/service/nfp/nfp.cpp162
-rw-r--r--src/core/hle/service/nfp/nfp.h26
-rw-r--r--src/core/hle/service/nfp/nfp_user.cpp17
-rw-r--r--src/core/hle/service/nfp/nfp_user.h16
-rw-r--r--src/core/hle/service/nifm/nifm.cpp126
-rw-r--r--src/core/hle/service/nifm/nifm.h26
-rw-r--r--src/core/hle/service/nifm/nifm_a.cpp25
-rw-r--r--src/core/hle/service/nifm/nifm_a.h18
-rw-r--r--src/core/hle/service/nifm/nifm_s.cpp25
-rw-r--r--src/core/hle/service/nifm/nifm_s.h18
-rw-r--r--src/core/hle/service/nifm/nifm_u.cpp25
-rw-r--r--src/core/hle/service/nifm/nifm_u.h18
-rw-r--r--src/core/hle/service/ns/ns.cpp6
-rw-r--r--src/core/hle/service/ns/ns.h6
-rw-r--r--src/core/hle/service/ns/pl_u.cpp87
-rw-r--r--src/core/hle/service/ns/pl_u.h8
-rw-r--r--src/core/hle/service/nvdrv/devices/nvdevice.h8
-rw-r--r--src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp27
-rw-r--r--src/core/hle/service/nvdrv/devices/nvdisp_disp0.h8
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp114
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h37
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp40
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_ctrl.h100
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp64
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h53
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp70
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_gpu.h72
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp32
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_nvdec.h38
-rw-r--r--src/core/hle/service/nvdrv/devices/nvmap.cpp65
-rw-r--r--src/core/hle/service/nvdrv/devices/nvmap.h50
-rw-r--r--src/core/hle/service/nvdrv/interface.cpp27
-rw-r--r--src/core/hle/service/nvdrv/interface.h6
-rw-r--r--src/core/hle/service/nvdrv/nvdrv.cpp12
-rw-r--r--src/core/hle/service/nvdrv/nvdrv.h6
-rw-r--r--src/core/hle/service/nvdrv/nvmemp.cpp14
-rw-r--r--src/core/hle/service/nvdrv/nvmemp.h10
-rw-r--r--src/core/hle/service/nvflinger/buffer_queue.cpp33
-rw-r--r--src/core/hle/service/nvflinger/buffer_queue.h8
-rw-r--r--src/core/hle/service/nvflinger/nvflinger.cpp16
-rw-r--r--src/core/hle/service/nvflinger/nvflinger.h6
-rw-r--r--src/core/hle/service/pctl/module.cpp146
-rw-r--r--src/core/hle/service/pctl/module.h28
-rw-r--r--src/core/hle/service/pctl/pctl.cpp17
-rw-r--r--src/core/hle/service/pctl/pctl.h14
-rw-r--r--src/core/hle/service/pctl/pctl_a.cpp32
-rw-r--r--src/core/hle/service/pctl/pctl_a.h22
-rw-r--r--src/core/hle/service/prepo/prepo.cpp43
-rw-r--r--src/core/hle/service/prepo/prepo.h23
-rw-r--r--src/core/hle/service/service.cpp96
-rw-r--r--src/core/hle/service/service.h2
-rw-r--r--src/core/hle/service/set/set.cpp50
-rw-r--r--src/core/hle/service/set/set.h32
-rw-r--r--src/core/hle/service/set/set_cal.cpp47
-rw-r--r--src/core/hle/service/set/set_cal.h17
-rw-r--r--src/core/hle/service/set/set_fd.cpp23
-rw-r--r--src/core/hle/service/set/set_fd.h17
-rw-r--r--src/core/hle/service/set/set_sys.cpp175
-rw-r--r--src/core/hle/service/set/set_sys.h20
-rw-r--r--src/core/hle/service/set/settings.cpp20
-rw-r--r--src/core/hle/service/set/settings.h14
-rw-r--r--src/core/hle/service/sm/controller.cpp14
-rw-r--r--src/core/hle/service/sm/controller.h6
-rw-r--r--src/core/hle/service/sm/sm.cpp19
-rw-r--r--src/core/hle/service/sm/sm.h12
-rw-r--r--src/core/hle/service/sockets/bsd.cpp114
-rw-r--r--src/core/hle/service/sockets/bsd.h (renamed from src/core/hle/service/sockets/bsd_u.h)12
-rw-r--r--src/core/hle/service/sockets/bsd_u.cpp88
-rw-r--r--src/core/hle/service/sockets/nsd.cpp32
-rw-r--r--src/core/hle/service/sockets/nsd.h18
-rw-r--r--src/core/hle/service/sockets/sfdnsres.cpp31
-rw-r--r--src/core/hle/service/sockets/sfdnsres.h8
-rw-r--r--src/core/hle/service/sockets/sockets.cpp14
-rw-r--r--src/core/hle/service/sockets/sockets.h6
-rw-r--r--src/core/hle/service/spl/csrng.cpp16
-rw-r--r--src/core/hle/service/spl/csrng.h16
-rw-r--r--src/core/hle/service/spl/module.cpp40
-rw-r--r--src/core/hle/service/spl/module.h27
-rw-r--r--src/core/hle/service/spl/spl.cpp45
-rw-r--r--src/core/hle/service/spl/spl.h16
-rw-r--r--src/core/hle/service/ssl/ssl.cpp119
-rw-r--r--src/core/hle/service/ssl/ssl.h24
-rw-r--r--src/core/hle/service/time/time.cpp95
-rw-r--r--src/core/hle/service/time/time.h34
-rw-r--r--src/core/hle/service/time/time_s.cpp17
-rw-r--r--src/core/hle/service/time/time_s.h6
-rw-r--r--src/core/hle/service/time/time_u.cpp17
-rw-r--r--src/core/hle/service/time/time_u.h6
-rw-r--r--src/core/hle/service/vi/vi.cpp461
-rw-r--r--src/core/hle/service/vi/vi.h47
-rw-r--r--src/core/hle/service/vi/vi_m.cpp21
-rw-r--r--src/core/hle/service/vi/vi_m.h23
-rw-r--r--src/core/hle/service/vi/vi_s.cpp21
-rw-r--r--src/core/hle/service/vi/vi_s.h23
-rw-r--r--src/core/hle/service/vi/vi_u.cpp22
-rw-r--r--src/core/hle/service/vi/vi_u.h23
-rw-r--r--src/core/hle/shared_page.cpp5
-rw-r--r--src/core/hw/hw.cpp10
-rw-r--r--src/core/hw/lcd.cpp8
-rw-r--r--src/core/loader/deconstructed_rom_directory.cpp20
-rw-r--r--src/core/loader/elf.cpp31
-rw-r--r--src/core/loader/linker.cpp4
-rw-r--r--src/core/loader/loader.cpp8
-rw-r--r--src/core/loader/loader.h1
-rw-r--r--src/core/loader/nro.cpp9
-rw-r--r--src/core/loader/nso.cpp20
-rw-r--r--src/core/loader/nso.h2
-rw-r--r--src/core/memory.cpp533
-rw-r--r--src/core/memory.h86
-rw-r--r--src/core/memory_hook.cpp11
-rw-r--r--src/core/memory_hook.h2
-rw-r--r--src/core/perf_stats.cpp3
-rw-r--r--src/core/settings.h15
-rw-r--r--src/core/telemetry_session.cpp17
-rw-r--r--src/core/telemetry_session.h4
-rw-r--r--src/core/tracer/recorder.cpp2
-rw-r--r--src/input_common/motion_emu.cpp5
-rw-r--r--src/input_common/sdl/sdl.cpp6
-rw-r--r--src/tests/core/arm/arm_test_common.cpp4
-rw-r--r--src/video_core/CMakeLists.txt25
-rw-r--r--src/video_core/command_processor.cpp48
-rw-r--r--src/video_core/command_processor.h2
-rw-r--r--src/video_core/debug_utils/debug_utils.cpp64
-rw-r--r--src/video_core/debug_utils/debug_utils.h163
-rw-r--r--src/video_core/engines/fermi_2d.cpp62
-rw-r--r--src/video_core/engines/fermi_2d.h94
-rw-r--r--src/video_core/engines/maxwell_3d.cpp387
-rw-r--r--src/video_core/engines/maxwell_3d.h706
-rw-r--r--src/video_core/engines/maxwell_dma.cpp69
-rw-r--r--src/video_core/engines/maxwell_dma.h155
-rw-r--r--src/video_core/engines/shader_bytecode.h735
-rw-r--r--src/video_core/gpu.cpp43
-rw-r--r--src/video_core/gpu.h78
-rw-r--r--src/video_core/macro_interpreter.cpp257
-rw-r--r--src/video_core/macro_interpreter.h164
-rw-r--r--src/video_core/memory_manager.cpp138
-rw-r--r--src/video_core/memory_manager.h37
-rw-r--r--src/video_core/rasterizer_interface.h64
-rw-r--r--src/video_core/renderer_base.cpp7
-rw-r--r--src/video_core/renderer_base.h40
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp758
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.h160
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer_cache.cpp1418
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer_cache.h520
-rw-r--r--src/video_core/renderer_opengl/gl_resource_manager.h202
-rw-r--r--src/video_core/renderer_opengl/gl_shader_decompiler.cpp1722
-rw-r--r--src/video_core/renderer_opengl/gl_shader_decompiler.h26
-rw-r--r--src/video_core/renderer_opengl/gl_shader_gen.cpp78
-rw-r--r--src/video_core/renderer_opengl/gl_shader_gen.h185
-rw-r--r--src/video_core/renderer_opengl/gl_shader_manager.cpp45
-rw-r--r--src/video_core/renderer_opengl/gl_shader_manager.h174
-rw-r--r--src/video_core/renderer_opengl/gl_shader_util.cpp103
-rw-r--r--src/video_core/renderer_opengl/gl_shader_util.h81
-rw-r--r--src/video_core/renderer_opengl/gl_state.cpp158
-rw-r--r--src/video_core/renderer_opengl/gl_state.h49
-rw-r--r--src/video_core/renderer_opengl/gl_stream_buffer.cpp182
-rw-r--r--src/video_core/renderer_opengl/gl_stream_buffer.h36
-rw-r--r--src/video_core/renderer_opengl/maxwell_to_gl.h204
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.cpp281
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.h23
-rw-r--r--src/video_core/textures/decoders.cpp136
-rw-r--r--src/video_core/textures/decoders.h31
-rw-r--r--src/video_core/textures/texture.h264
-rw-r--r--src/video_core/utils.h112
-rw-r--r--src/video_core/video_core.cpp6
-rw-r--r--src/video_core/video_core.h2
-rw-r--r--src/yuzu/CMakeLists.txt10
-rw-r--r--src/yuzu/about_dialog.cpp2
-rw-r--r--src/yuzu/bootmanager.cpp6
-rw-r--r--src/yuzu/configuration/config.cpp17
-rw-r--r--src/yuzu/configuration/configure_general.cpp21
-rw-r--r--src/yuzu/configuration/configure_general.ui111
-rw-r--r--src/yuzu/debugger/graphics/graphics_breakpoint_observer.cpp27
-rw-r--r--src/yuzu/debugger/graphics/graphics_breakpoint_observer.h33
-rw-r--r--src/yuzu/debugger/graphics/graphics_breakpoints.cpp212
-rw-r--r--src/yuzu/debugger/graphics/graphics_breakpoints.h46
-rw-r--r--src/yuzu/debugger/graphics/graphics_breakpoints_p.h36
-rw-r--r--src/yuzu/debugger/graphics/graphics_surface.cpp453
-rw-r--r--src/yuzu/debugger/graphics/graphics_surface.h97
-rw-r--r--src/yuzu/debugger/registers.cpp190
-rw-r--r--src/yuzu/debugger/registers.h42
-rw-r--r--src/yuzu/debugger/registers.ui40
-rw-r--r--src/yuzu/debugger/wait_tree.cpp141
-rw-r--r--src/yuzu/debugger/wait_tree.h54
-rw-r--r--src/yuzu/game_list.cpp5
-rw-r--r--src/yuzu/main.cpp145
-rw-r--r--src/yuzu/main.h29
-rw-r--r--src/yuzu/ui_settings.h4
-rw-r--r--src/yuzu_cmd/config.cpp13
-rw-r--r--src/yuzu_cmd/default_ini.h28
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2.cpp63
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2.h8
-rw-r--r--src/yuzu_cmd/yuzu.cpp69
-rw-r--r--src/yuzu_cmd/yuzu.rc2
349 files changed, 20262 insertions, 5708 deletions
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index d132ab969..f49a31612 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -31,17 +31,15 @@ add_library(common STATIC
bit_set.h
break_points.cpp
break_points.h
- chunk_file.h
- code_block.h
+ cityhash.cpp
+ cityhash.h
color.h
common_funcs.h
common_paths.h
common_types.h
file_util.cpp
file_util.h
- hash.cpp
hash.h
- linear_disk_cache.h
logging/backend.cpp
logging/backend.h
logging/filter.cpp
@@ -58,7 +56,6 @@ add_library(common STATIC
misc.cpp
param_package.cpp
param_package.h
- platform.h
quaternion.h
scm_rev.cpp
scm_rev.h
@@ -90,7 +87,7 @@ endif()
create_target_directory_groups(common)
-target_link_libraries(common PUBLIC Boost::boost microprofile)
+target_link_libraries(common PUBLIC Boost::boost fmt microprofile)
if (ARCHITECTURE_x86_64)
target_link_libraries(common PRIVATE xbyak)
endif()
diff --git a/src/common/assert.h b/src/common/assert.h
index 655446f34..3ee07f6a2 100644
--- a/src/common/assert.h
+++ b/src/common/assert.h
@@ -30,14 +30,15 @@ __declspec(noinline, noreturn)
#define ASSERT(_a_) \
do \
if (!(_a_)) { \
- assert_noinline_call([] { LOG_CRITICAL(Debug, "Assertion Failed!"); }); \
+ assert_noinline_call([] { NGLOG_CRITICAL(Debug, "Assertion Failed!"); }); \
} \
while (0)
#define ASSERT_MSG(_a_, ...) \
do \
if (!(_a_)) { \
- assert_noinline_call([&] { LOG_CRITICAL(Debug, "Assertion Failed!\n" __VA_ARGS__); }); \
+ assert_noinline_call( \
+ [&] { NGLOG_CRITICAL(Debug, "Assertion Failed!\n" __VA_ARGS__); }); \
} \
while (0)
diff --git a/src/common/bit_field.h b/src/common/bit_field.h
index 0cc0a1be0..65e357dec 100644
--- a/src/common/bit_field.h
+++ b/src/common/bit_field.h
@@ -115,7 +115,7 @@ private:
// 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.
- BitField& operator=(const BitField&) = default;
+ constexpr BitField& operator=(const BitField&) = default;
// StorageType 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
@@ -166,20 +166,20 @@ public:
// so that we can use this within unions
constexpr BitField() = default;
- FORCE_INLINE operator T() const {
+ constexpr FORCE_INLINE operator T() const {
return Value();
}
- FORCE_INLINE void Assign(const T& value) {
+ constexpr FORCE_INLINE void Assign(const T& value) {
storage = (storage & ~mask) | FormatValue(value);
}
- FORCE_INLINE T Value() const {
+ constexpr T Value() const {
return ExtractValue(storage);
}
// TODO: we may want to change this to explicit operator bool() if it's bug-free in VS2015
- FORCE_INLINE bool ToBool() const {
+ constexpr FORCE_INLINE bool ToBool() const {
return Value() != 0;
}
@@ -192,11 +192,6 @@ private:
static_assert(position < 8 * sizeof(T), "Invalid position");
static_assert(bits <= 8 * sizeof(T), "Invalid number of bits");
static_assert(bits > 0, "Invalid number of bits");
- static_assert(std::is_pod<T>::value, "Invalid base type");
+ static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable in a BitField");
};
#pragma pack()
-
-#if (__GNUC__ >= 5) || defined(__clang__) || defined(_MSC_VER)
-static_assert(std::is_trivially_copyable<BitField<0, 1, unsigned>>::value,
- "BitField must be trivially copyable");
-#endif
diff --git a/src/common/chunk_file.h b/src/common/chunk_file.h
deleted file mode 100644
index 972ef9039..000000000
--- a/src/common/chunk_file.h
+++ /dev/null
@@ -1,623 +0,0 @@
-// Copyright (C) 2003 Dolphin Project.
-
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, version 2.0 or later versions.
-
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License 2.0 for more details.
-
-// A copy of the GPL 2.0 should have been included with the program.
-// If not, see http://www.gnu.org/licenses/
-
-// Official SVN repository and contact information can be found at
-// http://code.google.com/p/dolphin-emu/
-
-#pragma once
-
-// Extremely simple serialization framework.
-
-// (mis)-features:
-// + Super fast
-// + Very simple
-// + Same code is used for serialization and deserializaition (in most cases)
-// - Zero backwards/forwards compatibility
-// - Serialization code for anything complex has to be manually written.
-
-#include <cstring>
-#include <deque>
-#include <list>
-#include <map>
-#include <set>
-#include <string>
-#include <type_traits>
-#include <utility>
-#include <vector>
-#include "common/assert.h"
-#include "common/common_types.h"
-#include "common/logging/log.h"
-
-template <class T>
-struct LinkedListItem : public T {
- LinkedListItem<T>* next;
-};
-
-class PointerWrap;
-
-class PointerWrapSection {
-public:
- PointerWrapSection(PointerWrap& p, int ver, const char* title)
- : p_(p), ver_(ver), title_(title) {}
- ~PointerWrapSection();
-
- bool operator==(const int& v) const {
- return ver_ == v;
- }
- bool operator!=(const int& v) const {
- return ver_ != v;
- }
- bool operator<=(const int& v) const {
- return ver_ <= v;
- }
- bool operator>=(const int& v) const {
- return ver_ >= v;
- }
- bool operator<(const int& v) const {
- return ver_ < v;
- }
- bool operator>(const int& v) const {
- return ver_ > v;
- }
-
- operator bool() const {
- return ver_ > 0;
- }
-
-private:
- PointerWrap& p_;
- int ver_;
- const char* title_;
-};
-
-// Wrapper class
-class PointerWrap {
-// This makes it a compile error if you forget to define DoState() on non-POD.
-// Which also can be a problem, for example struct tm is non-POD on linux, for whatever reason...
-#ifdef _MSC_VER
- template <typename T, bool isPOD = std::is_pod<T>::value,
- bool isPointer = std::is_pointer<T>::value>
-#else
- template <typename T, bool isPOD = __is_pod(T), bool isPointer = std::is_pointer<T>::value>
-#endif
- struct DoHelper {
- static void DoArray(PointerWrap* p, T* x, int count) {
- for (int i = 0; i < count; ++i)
- p->Do(x[i]);
- }
-
- static void Do(PointerWrap* p, T& x) {
- p->DoClass(x);
- }
- };
-
- template <typename T>
- struct DoHelper<T, true, false> {
- static void DoArray(PointerWrap* p, T* x, int count) {
- p->DoVoid((void*)x, sizeof(T) * count);
- }
-
- static void Do(PointerWrap* p, T& x) {
- p->DoVoid((void*)&x, sizeof(x));
- }
- };
-
-public:
- enum Mode {
- MODE_READ = 1, // load
- MODE_WRITE, // save
- MODE_MEASURE, // calculate size
- MODE_VERIFY, // compare
- };
-
- enum Error {
- ERROR_NONE = 0,
- ERROR_WARNING = 1,
- ERROR_FAILURE = 2,
- };
-
- u8** ptr;
- Mode mode;
- Error error;
-
-public:
- PointerWrap(u8** ptr_, Mode mode_) : ptr(ptr_), mode(mode_), error(ERROR_NONE) {}
- PointerWrap(unsigned char** ptr_, int mode_)
- : ptr((u8**)ptr_), mode((Mode)mode_), error(ERROR_NONE) {}
-
- PointerWrapSection Section(const char* title, int ver) {
- return Section(title, ver, ver);
- }
-
- // The returned object can be compared against the version that was loaded.
- // This can be used to support versions as old as minVer.
- // Version = 0 means the section was not found.
- PointerWrapSection Section(const char* title, int minVer, int ver) {
- char marker[16] = {0};
- int foundVersion = ver;
-
- strncpy(marker, title, sizeof(marker));
- if (!ExpectVoid(marker, sizeof(marker))) {
- // Might be before we added name markers for safety.
- if (foundVersion == 1 && ExpectVoid(&foundVersion, sizeof(foundVersion)))
- DoMarker(title);
- // Wasn't found, but maybe we can still load the state.
- else
- foundVersion = 0;
- } else
- Do(foundVersion);
-
- if (error == ERROR_FAILURE || foundVersion < minVer || foundVersion > ver) {
- LOG_ERROR(Common, "Savestate failure: wrong version %d found for %s", foundVersion,
- title);
- SetError(ERROR_FAILURE);
- return PointerWrapSection(*this, -1, title);
- }
- return PointerWrapSection(*this, foundVersion, title);
- }
-
- void SetMode(Mode mode_) {
- mode = mode_;
- }
- Mode GetMode() const {
- return mode;
- }
- u8** GetPPtr() {
- return ptr;
- }
- void SetError(Error error_) {
- if (error < error_)
- error = error_;
- if (error > ERROR_WARNING)
- mode = PointerWrap::MODE_MEASURE;
- }
-
- bool ExpectVoid(void* data, int size) {
- switch (mode) {
- case MODE_READ:
- if (memcmp(data, *ptr, size) != 0)
- return false;
- break;
- case MODE_WRITE:
- memcpy(*ptr, data, size);
- break;
- case MODE_MEASURE:
- break; // MODE_MEASURE - don't need to do anything
- case MODE_VERIFY:
- for (int i = 0; i < size; i++) {
- DEBUG_ASSERT_MSG(
- ((u8*)data)[i] == (*ptr)[i],
- "Savestate verification failure: %d (0x%X) (at %p) != %d (0x%X) (at %p).\n",
- ((u8*)data)[i], ((u8*)data)[i], &((u8*)data)[i], (*ptr)[i], (*ptr)[i],
- &(*ptr)[i]);
- }
- break;
- default:
- break; // throw an error?
- }
- (*ptr) += size;
- return true;
- }
-
- void DoVoid(void* data, int size) {
- switch (mode) {
- case MODE_READ:
- memcpy(data, *ptr, size);
- break;
- case MODE_WRITE:
- memcpy(*ptr, data, size);
- break;
- case MODE_MEASURE:
- break; // MODE_MEASURE - don't need to do anything
- case MODE_VERIFY:
- for (int i = 0; i < size; i++) {
- DEBUG_ASSERT_MSG(
- ((u8*)data)[i] == (*ptr)[i],
- "Savestate verification failure: %d (0x%X) (at %p) != %d (0x%X) (at %p).\n",
- ((u8*)data)[i], ((u8*)data)[i], &((u8*)data)[i], (*ptr)[i], (*ptr)[i],
- &(*ptr)[i]);
- }
- break;
- default:
- break; // throw an error?
- }
- (*ptr) += size;
- }
-
- template <class K, class T>
- void Do(std::map<K, T*>& x) {
- if (mode == MODE_READ) {
- for (auto it = x.begin(), end = x.end(); it != end; ++it) {
- if (it->second != nullptr)
- delete it->second;
- }
- }
- T* dv = nullptr;
- DoMap(x, dv);
- }
-
- template <class K, class T>
- void Do(std::map<K, T>& x) {
- T dv = T();
- DoMap(x, dv);
- }
-
- template <class K, class T>
- void DoMap(std::map<K, T>& x, T& default_val) {
- unsigned int number = (unsigned int)x.size();
- Do(number);
- switch (mode) {
- case MODE_READ: {
- x.clear();
- while (number > 0) {
- K first = K();
- Do(first);
- T second = default_val;
- Do(second);
- x[first] = second;
- --number;
- }
- } break;
- case MODE_WRITE:
- case MODE_MEASURE:
- case MODE_VERIFY: {
- typename std::map<K, T>::iterator itr = x.begin();
- while (number > 0) {
- K first = itr->first;
- Do(first);
- Do(itr->second);
- --number;
- ++itr;
- }
- } break;
- }
- }
-
- template <class K, class T>
- void Do(std::multimap<K, T*>& x) {
- if (mode == MODE_READ) {
- for (auto it = x.begin(), end = x.end(); it != end; ++it) {
- if (it->second != nullptr)
- delete it->second;
- }
- }
- T* dv = nullptr;
- DoMultimap(x, dv);
- }
-
- template <class K, class T>
- void Do(std::multimap<K, T>& x) {
- T dv = T();
- DoMultimap(x, dv);
- }
-
- template <class K, class T>
- void DoMultimap(std::multimap<K, T>& x, T& default_val) {
- unsigned int number = (unsigned int)x.size();
- Do(number);
- switch (mode) {
- case MODE_READ: {
- x.clear();
- while (number > 0) {
- K first = K();
- Do(first);
- T second = default_val;
- Do(second);
- x.insert(std::make_pair(first, second));
- --number;
- }
- } break;
- case MODE_WRITE:
- case MODE_MEASURE:
- case MODE_VERIFY: {
- typename std::multimap<K, T>::iterator itr = x.begin();
- while (number > 0) {
- Do(itr->first);
- Do(itr->second);
- --number;
- ++itr;
- }
- } break;
- }
- }
-
- // Store vectors.
- template <class T>
- void Do(std::vector<T*>& x) {
- T* dv = nullptr;
- DoVector(x, dv);
- }
-
- template <class T>
- void Do(std::vector<T>& x) {
- T dv = T();
- DoVector(x, dv);
- }
-
- template <class T>
- void DoPOD(std::vector<T>& x) {
- T dv = T();
- DoVectorPOD(x, dv);
- }
-
- template <class T>
- void Do(std::vector<T>& x, T& default_val) {
- DoVector(x, default_val);
- }
-
- template <class T>
- void DoVector(std::vector<T>& x, T& default_val) {
- u32 vec_size = (u32)x.size();
- Do(vec_size);
- x.resize(vec_size, default_val);
- if (vec_size > 0)
- DoArray(&x[0], vec_size);
- }
-
- template <class T>
- void DoVectorPOD(std::vector<T>& x, T& default_val) {
- u32 vec_size = (u32)x.size();
- Do(vec_size);
- x.resize(vec_size, default_val);
- if (vec_size > 0)
- DoArray(&x[0], vec_size);
- }
-
- // Store deques.
- template <class T>
- void Do(std::deque<T*>& x) {
- T* dv = nullptr;
- DoDeque(x, dv);
- }
-
- template <class T>
- void Do(std::deque<T>& x) {
- T dv = T();
- DoDeque(x, dv);
- }
-
- template <class T>
- void DoDeque(std::deque<T>& x, T& default_val) {
- u32 deq_size = (u32)x.size();
- Do(deq_size);
- x.resize(deq_size, default_val);
- u32 i;
- for (i = 0; i < deq_size; i++)
- Do(x[i]);
- }
-
- // Store STL lists.
- template <class T>
- void Do(std::list<T*>& x) {
- T* dv = nullptr;
- Do(x, dv);
- }
-
- template <class T>
- void Do(std::list<T>& x) {
- T dv = T();
- DoList(x, dv);
- }
-
- template <class T>
- void Do(std::list<T>& x, T& default_val) {
- DoList(x, default_val);
- }
-
- template <class T>
- void DoList(std::list<T>& x, T& default_val) {
- u32 list_size = (u32)x.size();
- Do(list_size);
- x.resize(list_size, default_val);
-
- typename std::list<T>::iterator itr, end;
- for (itr = x.begin(), end = x.end(); itr != end; ++itr)
- Do(*itr);
- }
-
- // Store STL sets.
- template <class T>
- void Do(std::set<T*>& x) {
- if (mode == MODE_READ) {
- for (auto it = x.begin(), end = x.end(); it != end; ++it) {
- if (*it != nullptr)
- delete *it;
- }
- }
- DoSet(x);
- }
-
- template <class T>
- void Do(std::set<T>& x) {
- DoSet(x);
- }
-
- template <class T>
- void DoSet(std::set<T>& x) {
- unsigned int number = (unsigned int)x.size();
- Do(number);
-
- switch (mode) {
- case MODE_READ: {
- x.clear();
- while (number-- > 0) {
- T it = T();
- Do(it);
- x.insert(it);
- }
- } break;
- case MODE_WRITE:
- case MODE_MEASURE:
- case MODE_VERIFY: {
- typename std::set<T>::iterator itr = x.begin();
- while (number-- > 0)
- Do(*itr++);
- } break;
-
- default:
- LOG_ERROR(Common, "Savestate error: invalid mode %d.", mode);
- }
- }
-
- // Store strings.
- void Do(std::string& x) {
- int stringLen = (int)x.length() + 1;
- Do(stringLen);
-
- switch (mode) {
- case MODE_READ:
- x = (char*)*ptr;
- break;
- case MODE_WRITE:
- memcpy(*ptr, x.c_str(), stringLen);
- break;
- case MODE_MEASURE:
- break;
- case MODE_VERIFY:
- DEBUG_ASSERT_MSG((x == (char*)*ptr),
- "Savestate verification failure: \"%s\" != \"%s\" (at %p).\n",
- x.c_str(), (char*)*ptr, ptr);
- break;
- }
- (*ptr) += stringLen;
- }
-
- void Do(std::wstring& x) {
- int stringLen = sizeof(wchar_t) * ((int)x.length() + 1);
- Do(stringLen);
-
- switch (mode) {
- case MODE_READ:
- x = (wchar_t*)*ptr;
- break;
- case MODE_WRITE:
- memcpy(*ptr, x.c_str(), stringLen);
- break;
- case MODE_MEASURE:
- break;
- case MODE_VERIFY:
- DEBUG_ASSERT_MSG((x == (wchar_t*)*ptr),
- "Savestate verification failure: \"%ls\" != \"%ls\" (at %p).\n",
- x.c_str(), (wchar_t*)*ptr, ptr);
- break;
- }
- (*ptr) += stringLen;
- }
-
- template <class T>
- void DoClass(T& x) {
- x.DoState(*this);
- }
-
- template <class T>
- void DoClass(T*& x) {
- if (mode == MODE_READ) {
- if (x != nullptr)
- delete x;
- x = new T();
- }
- x->DoState(*this);
- }
-
- template <class T>
- void DoArray(T* x, int count) {
- DoHelper<T>::DoArray(this, x, count);
- }
-
- template <class T>
- void Do(T& x) {
- DoHelper<T>::Do(this, x);
- }
-
- template <class T>
- void DoPOD(T& x) {
- DoHelper<T>::Do(this, x);
- }
-
- template <class T>
- void DoPointer(T*& x, T* const base) {
- // pointers can be more than 2^31 apart, but you're using this function wrong if you need
- // that much range
- s32 offset = x - base;
- Do(offset);
- if (mode == MODE_READ)
- x = base + offset;
- }
-
- template <class T, LinkedListItem<T>* (*TNew)(), void (*TFree)(LinkedListItem<T>*),
- void (*TDo)(PointerWrap&, T*)>
- void DoLinkedList(LinkedListItem<T>*& list_start, LinkedListItem<T>** list_end = nullptr) {
- LinkedListItem<T>* list_cur = list_start;
- LinkedListItem<T>* prev = nullptr;
-
- while (true) {
- u8 shouldExist = (list_cur ? 1 : 0);
- Do(shouldExist);
- if (shouldExist == 1) {
- LinkedListItem<T>* cur = list_cur ? list_cur : TNew();
- TDo(*this, (T*)cur);
- if (!list_cur) {
- if (mode == MODE_READ) {
- cur->next = nullptr;
- list_cur = cur;
- if (prev)
- prev->next = cur;
- else
- list_start = cur;
- } else {
- TFree(cur);
- continue;
- }
- }
- } else {
- if (mode == MODE_READ) {
- if (prev)
- prev->next = nullptr;
- if (list_end)
- *list_end = prev;
- if (list_cur) {
- if (list_start == list_cur)
- list_start = nullptr;
- do {
- LinkedListItem<T>* next = list_cur->next;
- TFree(list_cur);
- list_cur = next;
- } while (list_cur);
- }
- }
- break;
- }
- prev = list_cur;
- list_cur = list_cur->next;
- }
- }
-
- void DoMarker(const char* prevName, u32 arbitraryNumber = 0x42) {
- u32 cookie = arbitraryNumber;
- Do(cookie);
- if (mode == PointerWrap::MODE_READ && cookie != arbitraryNumber) {
- LOG_ERROR(Common,
- "After \"%s\", found %d (0x%X) instead of save marker %d (0x%X). "
- "Aborting savestate load...",
- prevName, cookie, cookie, arbitraryNumber, arbitraryNumber);
- SetError(ERROR_FAILURE);
- }
- }
-};
-
-inline PointerWrapSection::~PointerWrapSection() {
- if (ver_ > 0) {
- p_.DoMarker(title_);
- }
-}
diff --git a/src/common/cityhash.cpp b/src/common/cityhash.cpp
new file mode 100644
index 000000000..de31ffbd8
--- /dev/null
+++ b/src/common/cityhash.cpp
@@ -0,0 +1,340 @@
+// Copyright (c) 2011 Google, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+// CityHash, by Geoff Pike and Jyrki Alakuijala
+//
+// This file provides CityHash64() and related functions.
+//
+// It's probably possible to create even faster hash functions by
+// writing a program that systematically explores some of the space of
+// possible hash functions, by using SIMD instructions, or by
+// compromising on hash quality.
+
+#include <algorithm>
+#include <string.h> // for memcpy and memset
+#include "cityhash.h"
+#include "common/swap.h"
+
+// #include "config.h"
+#ifdef __GNUC__
+#define HAVE_BUILTIN_EXPECT 1
+#endif
+#ifdef COMMON_BIG_ENDIAN
+#define WORDS_BIGENDIAN 1
+#endif
+
+using namespace std;
+
+typedef uint8_t uint8;
+typedef uint32_t uint32;
+typedef uint64_t uint64;
+
+namespace Common {
+
+static uint64 UNALIGNED_LOAD64(const char* p) {
+ uint64 result;
+ memcpy(&result, p, sizeof(result));
+ return result;
+}
+
+static uint32 UNALIGNED_LOAD32(const char* p) {
+ uint32 result;
+ memcpy(&result, p, sizeof(result));
+ return result;
+}
+
+#ifdef WORDS_BIGENDIAN
+#define uint32_in_expected_order(x) (swap32(x))
+#define uint64_in_expected_order(x) (swap64(x))
+#else
+#define uint32_in_expected_order(x) (x)
+#define uint64_in_expected_order(x) (x)
+#endif
+
+#if !defined(LIKELY)
+#if HAVE_BUILTIN_EXPECT
+#define LIKELY(x) (__builtin_expect(!!(x), 1))
+#else
+#define LIKELY(x) (x)
+#endif
+#endif
+
+static uint64 Fetch64(const char* p) {
+ return uint64_in_expected_order(UNALIGNED_LOAD64(p));
+}
+
+static uint32 Fetch32(const char* p) {
+ return uint32_in_expected_order(UNALIGNED_LOAD32(p));
+}
+
+// Some primes between 2^63 and 2^64 for various uses.
+static const uint64 k0 = 0xc3a5c85c97cb3127ULL;
+static const uint64 k1 = 0xb492b66fbe98f273ULL;
+static const uint64 k2 = 0x9ae16a3b2f90404fULL;
+
+// Bitwise right rotate. Normally this will compile to a single
+// instruction, especially if the shift is a manifest constant.
+static uint64 Rotate(uint64 val, int shift) {
+ // Avoid shifting by 64: doing so yields an undefined result.
+ return shift == 0 ? val : ((val >> shift) | (val << (64 - shift)));
+}
+
+static uint64 ShiftMix(uint64 val) {
+ return val ^ (val >> 47);
+}
+
+static uint64 HashLen16(uint64 u, uint64 v) {
+ return Hash128to64(uint128(u, v));
+}
+
+static uint64 HashLen16(uint64 u, uint64 v, uint64 mul) {
+ // Murmur-inspired hashing.
+ uint64 a = (u ^ v) * mul;
+ a ^= (a >> 47);
+ uint64 b = (v ^ a) * mul;
+ b ^= (b >> 47);
+ b *= mul;
+ return b;
+}
+
+static uint64 HashLen0to16(const char* s, size_t len) {
+ if (len >= 8) {
+ uint64 mul = k2 + len * 2;
+ uint64 a = Fetch64(s) + k2;
+ uint64 b = Fetch64(s + len - 8);
+ uint64 c = Rotate(b, 37) * mul + a;
+ uint64 d = (Rotate(a, 25) + b) * mul;
+ return HashLen16(c, d, mul);
+ }
+ if (len >= 4) {
+ uint64 mul = k2 + len * 2;
+ uint64 a = Fetch32(s);
+ return HashLen16(len + (a << 3), Fetch32(s + len - 4), mul);
+ }
+ if (len > 0) {
+ uint8 a = s[0];
+ uint8 b = s[len >> 1];
+ uint8 c = s[len - 1];
+ uint32 y = static_cast<uint32>(a) + (static_cast<uint32>(b) << 8);
+ uint32 z = static_cast<uint32>(len) + (static_cast<uint32>(c) << 2);
+ return ShiftMix(y * k2 ^ z * k0) * k2;
+ }
+ return k2;
+}
+
+// This probably works well for 16-byte strings as well, but it may be overkill
+// in that case.
+static uint64 HashLen17to32(const char* s, size_t len) {
+ uint64 mul = k2 + len * 2;
+ uint64 a = Fetch64(s) * k1;
+ uint64 b = Fetch64(s + 8);
+ uint64 c = Fetch64(s + len - 8) * mul;
+ uint64 d = Fetch64(s + len - 16) * k2;
+ return HashLen16(Rotate(a + b, 43) + Rotate(c, 30) + d, a + Rotate(b + k2, 18) + c, mul);
+}
+
+// Return a 16-byte hash for 48 bytes. Quick and dirty.
+// Callers do best to use "random-looking" values for a and b.
+static pair<uint64, uint64> WeakHashLen32WithSeeds(uint64 w, uint64 x, uint64 y, uint64 z, uint64 a,
+ uint64 b) {
+ a += w;
+ b = Rotate(b + a + z, 21);
+ uint64 c = a;
+ a += x;
+ a += y;
+ b += Rotate(a, 44);
+ return make_pair(a + z, b + c);
+}
+
+// Return a 16-byte hash for s[0] ... s[31], a, and b. Quick and dirty.
+static pair<uint64, uint64> WeakHashLen32WithSeeds(const char* s, uint64 a, uint64 b) {
+ return WeakHashLen32WithSeeds(Fetch64(s), Fetch64(s + 8), Fetch64(s + 16), Fetch64(s + 24), a,
+ b);
+}
+
+// Return an 8-byte hash for 33 to 64 bytes.
+static uint64 HashLen33to64(const char* s, size_t len) {
+ uint64 mul = k2 + len * 2;
+ uint64 a = Fetch64(s) * k2;
+ uint64 b = Fetch64(s + 8);
+ uint64 c = Fetch64(s + len - 24);
+ uint64 d = Fetch64(s + len - 32);
+ uint64 e = Fetch64(s + 16) * k2;
+ uint64 f = Fetch64(s + 24) * 9;
+ uint64 g = Fetch64(s + len - 8);
+ uint64 h = Fetch64(s + len - 16) * mul;
+ uint64 u = Rotate(a + g, 43) + (Rotate(b, 30) + c) * 9;
+ uint64 v = ((a + g) ^ d) + f + 1;
+ uint64 w = swap64((u + v) * mul) + h;
+ uint64 x = Rotate(e + f, 42) + c;
+ uint64 y = (swap64((v + w) * mul) + g) * mul;
+ uint64 z = e + f + c;
+ a = swap64((x + z) * mul + y) + b;
+ b = ShiftMix((z + a) * mul + d + h) * mul;
+ return b + x;
+}
+
+uint64 CityHash64(const char* s, size_t len) {
+ if (len <= 32) {
+ if (len <= 16) {
+ return HashLen0to16(s, len);
+ } else {
+ return HashLen17to32(s, len);
+ }
+ } else if (len <= 64) {
+ return HashLen33to64(s, len);
+ }
+
+ // For strings over 64 bytes we hash the end first, and then as we
+ // loop we keep 56 bytes of state: v, w, x, y, and z.
+ uint64 x = Fetch64(s + len - 40);
+ uint64 y = Fetch64(s + len - 16) + Fetch64(s + len - 56);
+ uint64 z = HashLen16(Fetch64(s + len - 48) + len, Fetch64(s + len - 24));
+ pair<uint64, uint64> v = WeakHashLen32WithSeeds(s + len - 64, len, z);
+ pair<uint64, uint64> w = WeakHashLen32WithSeeds(s + len - 32, y + k1, x);
+ x = x * k1 + Fetch64(s);
+
+ // Decrease len to the nearest multiple of 64, and operate on 64-byte chunks.
+ len = (len - 1) & ~static_cast<size_t>(63);
+ do {
+ x = Rotate(x + y + v.first + Fetch64(s + 8), 37) * k1;
+ y = Rotate(y + v.second + Fetch64(s + 48), 42) * k1;
+ x ^= w.second;
+ y += v.first + Fetch64(s + 40);
+ z = Rotate(z + w.first, 33) * k1;
+ v = WeakHashLen32WithSeeds(s, v.second * k1, x + w.first);
+ w = WeakHashLen32WithSeeds(s + 32, z + w.second, y + Fetch64(s + 16));
+ std::swap(z, x);
+ s += 64;
+ len -= 64;
+ } while (len != 0);
+ return HashLen16(HashLen16(v.first, w.first) + ShiftMix(y) * k1 + z,
+ HashLen16(v.second, w.second) + x);
+}
+
+uint64 CityHash64WithSeed(const char* s, size_t len, uint64 seed) {
+ return CityHash64WithSeeds(s, len, k2, seed);
+}
+
+uint64 CityHash64WithSeeds(const char* s, size_t len, uint64 seed0, uint64 seed1) {
+ return HashLen16(CityHash64(s, len) - seed0, seed1);
+}
+
+// A subroutine for CityHash128(). Returns a decent 128-bit hash for strings
+// of any length representable in signed long. Based on City and Murmur.
+static uint128 CityMurmur(const char* s, size_t len, uint128 seed) {
+ uint64 a = Uint128Low64(seed);
+ uint64 b = Uint128High64(seed);
+ uint64 c = 0;
+ uint64 d = 0;
+ signed long l = static_cast<long>(len) - 16;
+ if (l <= 0) { // len <= 16
+ a = ShiftMix(a * k1) * k1;
+ c = b * k1 + HashLen0to16(s, len);
+ d = ShiftMix(a + (len >= 8 ? Fetch64(s) : c));
+ } else { // len > 16
+ c = HashLen16(Fetch64(s + len - 8) + k1, a);
+ d = HashLen16(b + len, c + Fetch64(s + len - 16));
+ a += d;
+ do {
+ a ^= ShiftMix(Fetch64(s) * k1) * k1;
+ a *= k1;
+ b ^= a;
+ c ^= ShiftMix(Fetch64(s + 8) * k1) * k1;
+ c *= k1;
+ d ^= c;
+ s += 16;
+ l -= 16;
+ } while (l > 0);
+ }
+ a = HashLen16(a, c);
+ b = HashLen16(d, b);
+ return uint128(a ^ b, HashLen16(b, a));
+}
+
+uint128 CityHash128WithSeed(const char* s, size_t len, uint128 seed) {
+ if (len < 128) {
+ return CityMurmur(s, len, seed);
+ }
+
+ // We expect len >= 128 to be the common case. Keep 56 bytes of state:
+ // v, w, x, y, and z.
+ pair<uint64, uint64> v, w;
+ uint64 x = Uint128Low64(seed);
+ uint64 y = Uint128High64(seed);
+ uint64 z = len * k1;
+ v.first = Rotate(y ^ k1, 49) * k1 + Fetch64(s);
+ v.second = Rotate(v.first, 42) * k1 + Fetch64(s + 8);
+ w.first = Rotate(y + z, 35) * k1 + x;
+ w.second = Rotate(x + Fetch64(s + 88), 53) * k1;
+
+ // This is the same inner loop as CityHash64(), manually unrolled.
+ do {
+ x = Rotate(x + y + v.first + Fetch64(s + 8), 37) * k1;
+ y = Rotate(y + v.second + Fetch64(s + 48), 42) * k1;
+ x ^= w.second;
+ y += v.first + Fetch64(s + 40);
+ z = Rotate(z + w.first, 33) * k1;
+ v = WeakHashLen32WithSeeds(s, v.second * k1, x + w.first);
+ w = WeakHashLen32WithSeeds(s + 32, z + w.second, y + Fetch64(s + 16));
+ std::swap(z, x);
+ s += 64;
+ x = Rotate(x + y + v.first + Fetch64(s + 8), 37) * k1;
+ y = Rotate(y + v.second + Fetch64(s + 48), 42) * k1;
+ x ^= w.second;
+ y += v.first + Fetch64(s + 40);
+ z = Rotate(z + w.first, 33) * k1;
+ v = WeakHashLen32WithSeeds(s, v.second * k1, x + w.first);
+ w = WeakHashLen32WithSeeds(s + 32, z + w.second, y + Fetch64(s + 16));
+ std::swap(z, x);
+ s += 64;
+ len -= 128;
+ } while (LIKELY(len >= 128));
+ x += Rotate(v.first + z, 49) * k0;
+ y = y * k0 + Rotate(w.second, 37);
+ z = z * k0 + Rotate(w.first, 27);
+ w.first *= 9;
+ v.first *= k0;
+ // If 0 < len < 128, hash up to 4 chunks of 32 bytes each from the end of s.
+ for (size_t tail_done = 0; tail_done < len;) {
+ tail_done += 32;
+ y = Rotate(x + y, 42) * k0 + v.second;
+ w.first += Fetch64(s + len - tail_done + 16);
+ x = x * k0 + w.first;
+ z += w.second + Fetch64(s + len - tail_done);
+ w.second += v.first;
+ v = WeakHashLen32WithSeeds(s + len - tail_done, v.first + z, v.second);
+ v.first *= k0;
+ }
+ // At this point our 56 bytes of state should contain more than
+ // enough information for a strong 128-bit hash. We use two
+ // different 56-byte-to-8-byte hashes to get a 16-byte final result.
+ x = HashLen16(x, v.first);
+ y = HashLen16(y + z, w.first);
+ return uint128(HashLen16(x + v.second, w.second) + y, HashLen16(x + w.second, y + v.second));
+}
+
+uint128 CityHash128(const char* s, size_t len) {
+ return len >= 16
+ ? CityHash128WithSeed(s + 16, len - 16, uint128(Fetch64(s), Fetch64(s + 8) + k0))
+ : CityHash128WithSeed(s, len, uint128(k0, k1));
+}
+
+} // namespace Common
diff --git a/src/common/cityhash.h b/src/common/cityhash.h
new file mode 100644
index 000000000..bcebdb150
--- /dev/null
+++ b/src/common/cityhash.h
@@ -0,0 +1,110 @@
+// Copyright (c) 2011 Google, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+// CityHash, by Geoff Pike and Jyrki Alakuijala
+//
+// http://code.google.com/p/cityhash/
+//
+// This file provides a few functions for hashing strings. All of them are
+// high-quality functions in the sense that they pass standard tests such
+// as Austin Appleby's SMHasher. They are also fast.
+//
+// For 64-bit x86 code, on short strings, we don't know of anything faster than
+// CityHash64 that is of comparable quality. We believe our nearest competitor
+// is Murmur3. For 64-bit x86 code, CityHash64 is an excellent choice for hash
+// tables and most other hashing (excluding cryptography).
+//
+// For 64-bit x86 code, on long strings, the picture is more complicated.
+// On many recent Intel CPUs, such as Nehalem, Westmere, Sandy Bridge, etc.,
+// CityHashCrc128 appears to be faster than all competitors of comparable
+// quality. CityHash128 is also good but not quite as fast. We believe our
+// nearest competitor is Bob Jenkins' Spooky. We don't have great data for
+// other 64-bit CPUs, but for long strings we know that Spooky is slightly
+// faster than CityHash on some relatively recent AMD x86-64 CPUs, for example.
+// Note that CityHashCrc128 is declared in citycrc.h.
+//
+// For 32-bit x86 code, we don't know of anything faster than CityHash32 that
+// is of comparable quality. We believe our nearest competitor is Murmur3A.
+// (On 64-bit CPUs, it is typically faster to use the other CityHash variants.)
+//
+// Functions in the CityHash family are not suitable for cryptography.
+//
+// Please see CityHash's README file for more details on our performance
+// measurements and so on.
+//
+// WARNING: This code has been only lightly tested on big-endian platforms!
+// It is known to work well on little-endian platforms that have a small penalty
+// for unaligned reads, such as current Intel and AMD moderate-to-high-end CPUs.
+// It should work on all 32-bit and 64-bit platforms that allow unaligned reads;
+// bug reports are welcome.
+//
+// By the way, for some hash functions, given strings a and b, the hash
+// of a+b is easily derived from the hashes of a and b. This property
+// doesn't hold for any hash functions in this file.
+
+#pragma once
+
+#include <utility>
+#include <stdint.h>
+#include <stdlib.h> // for size_t.
+
+namespace Common {
+
+typedef std::pair<uint64_t, uint64_t> uint128;
+
+inline uint64_t Uint128Low64(const uint128& x) {
+ return x.first;
+}
+inline uint64_t Uint128High64(const uint128& x) {
+ return x.second;
+}
+
+// Hash function for a byte array.
+uint64_t CityHash64(const char* buf, size_t len);
+
+// Hash function for a byte array. For convenience, a 64-bit seed is also
+// hashed into the result.
+uint64_t CityHash64WithSeed(const char* buf, size_t len, uint64_t seed);
+
+// Hash function for a byte array. For convenience, two seeds are also
+// hashed into the result.
+uint64_t CityHash64WithSeeds(const char* buf, size_t len, uint64_t seed0, uint64_t seed1);
+
+// Hash function for a byte array.
+uint128 CityHash128(const char* s, size_t len);
+
+// Hash function for a byte array. For convenience, a 128-bit seed is also
+// hashed into the result.
+uint128 CityHash128WithSeed(const char* s, size_t len, uint128 seed);
+
+// Hash 128 input bits down to 64 bits of output.
+// This is intended to be a reasonably good hash function.
+inline uint64_t Hash128to64(const uint128& x) {
+ // Murmur-inspired hashing.
+ const uint64_t kMul = 0x9ddfea08eb382d69ULL;
+ uint64_t a = (Uint128Low64(x) ^ Uint128High64(x)) * kMul;
+ a ^= (a >> 47);
+ uint64_t b = (Uint128High64(x) ^ a) * kMul;
+ b ^= (b >> 47);
+ b *= kMul;
+ return b;
+}
+
+} // namespace Common
diff --git a/src/common/code_block.h b/src/common/code_block.h
deleted file mode 100644
index 6a55a8e30..000000000
--- a/src/common/code_block.h
+++ /dev/null
@@ -1,85 +0,0 @@
-// Copyright 2013 Dolphin Emulator Project
-// Licensed under GPLv2
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <cstddef>
-#include "common/common_types.h"
-#include "common/memory_util.h"
-
-// Everything that needs to generate code should inherit from this.
-// You get memory management for free, plus, you can use all emitter functions without
-// having to prefix them with gen-> or something similar.
-// Example implementation:
-// class JIT : public CodeBlock<ARMXEmitter> {}
-template <class T>
-class CodeBlock : public T, NonCopyable {
-private:
- // A privately used function to set the executable RAM space to something invalid.
- // For debugging usefulness it should be used to set the RAM to a host specific breakpoint
- // instruction
- virtual void PoisonMemory() = 0;
-
-protected:
- u8* region;
- size_t region_size;
-
-public:
- CodeBlock() : region(nullptr), region_size(0) {}
- virtual ~CodeBlock() {
- if (region)
- FreeCodeSpace();
- }
-
- // Call this before you generate any code.
- void AllocCodeSpace(int size) {
- region_size = size;
- region = (u8*)AllocateExecutableMemory(region_size);
- T::SetCodePtr(region);
- }
-
- // Always clear code space with breakpoints, so that if someone accidentally executes
- // uninitialized, it just breaks into the debugger.
- void ClearCodeSpace() {
- PoisonMemory();
- ResetCodePtr();
- }
-
- // Call this when shutting down. Don't rely on the destructor, even though it'll do the job.
- void FreeCodeSpace() {
-#ifdef __SYMBIAN32__
- ResetExecutableMemory(region);
-#else
- FreeMemoryPages(region, region_size);
-#endif
- region = nullptr;
- region_size = 0;
- }
-
- bool IsInSpace(const u8* ptr) {
- return (ptr >= region) && (ptr < (region + region_size));
- }
-
- // Cannot currently be undone. Will write protect the entire code region.
- // Start over if you need to change the code (call FreeCodeSpace(), AllocCodeSpace()).
- void WriteProtect() {
- WriteProtectMemory(region, region_size, true);
- }
-
- void ResetCodePtr() {
- T::SetCodePtr(region);
- }
-
- size_t GetSpaceLeft() const {
- return region_size - (T::GetCodePtr() - region);
- }
-
- u8* GetBasePtr() {
- return region;
- }
-
- size_t GetOffset(const u8* ptr) const {
- return ptr - region;
- }
-};
diff --git a/src/common/common_funcs.h b/src/common/common_funcs.h
index 6f0604958..7cf7b7997 100644
--- a/src/common/common_funcs.h
+++ b/src/common/common_funcs.h
@@ -9,8 +9,6 @@
#endif
#include "common/common_types.h"
-#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
-
/// Textually concatenates two tokens. The double-expansion is required by the C preprocessor.
#define CONCAT2(x, y) DO_CONCAT2(x, y)
#define DO_CONCAT2(x, y) x##y
@@ -74,11 +72,6 @@ inline u64 _rotr64(u64 x, unsigned int shift) {
#else // _MSC_VER
-#if (_MSC_VER < 1900)
-// Function Cross-Compatibility
-#define snprintf _snprintf
-#endif
-
// Locale Cross-Compatibility
#define locale_t _locale_t
diff --git a/src/common/common_types.h b/src/common/common_types.h
index 844d34965..6b1766dca 100644
--- a/src/common/common_types.h
+++ b/src/common/common_types.h
@@ -27,29 +27,23 @@
#include <array>
#include <cstdint>
-#ifdef _MSC_VER
-#ifndef __func__
-#define __func__ __FUNCTION__
-#endif
-#endif
+using u8 = std::uint8_t; ///< 8-bit unsigned byte
+using u16 = std::uint16_t; ///< 16-bit unsigned short
+using u32 = std::uint32_t; ///< 32-bit unsigned word
+using u64 = std::uint64_t; ///< 64-bit unsigned int
-typedef std::uint8_t u8; ///< 8-bit unsigned byte
-typedef std::uint16_t u16; ///< 16-bit unsigned short
-typedef std::uint32_t u32; ///< 32-bit unsigned word
-typedef std::uint64_t u64; ///< 64-bit unsigned int
+using s8 = std::int8_t; ///< 8-bit signed byte
+using s16 = std::int16_t; ///< 16-bit signed short
+using s32 = std::int32_t; ///< 32-bit signed word
+using s64 = std::int64_t; ///< 64-bit signed int
-typedef std::int8_t s8; ///< 8-bit signed byte
-typedef std::int16_t s16; ///< 16-bit signed short
-typedef std::int32_t s32; ///< 32-bit signed word
-typedef std::int64_t s64; ///< 64-bit signed int
-
-typedef float f32; ///< 32-bit floating point
-typedef double f64; ///< 64-bit floating point
+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.
-typedef u64 VAddr; ///< Represents a pointer in the userspace virtual address space.
-typedef u64 PAddr; ///< 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 u128 = std::array<std::uint64_t, 2>;
static_assert(sizeof(u128) == 16, "u128 must be 128 bits wide");
diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp
index 4e1d702f7..2d0b81c6e 100644
--- a/src/common/file_util.cpp
+++ b/src/common/file_util.cpp
@@ -118,7 +118,7 @@ bool IsDirectory(const std::string& filename) {
#endif
if (result < 0) {
- LOG_DEBUG(Common_Filesystem, "stat failed on %s: %s", filename.c_str(), GetLastErrorMsg());
+ NGLOG_DEBUG(Common_Filesystem, "stat failed on {}: {}", filename, GetLastErrorMsg());
return false;
}
@@ -128,31 +128,29 @@ bool IsDirectory(const std::string& filename) {
// 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 %s", filename.c_str());
+ NGLOG_TRACE(Common_Filesystem, "file {}", filename);
// Return true because we care about the file no
// being there, not the actual delete.
if (!Exists(filename)) {
- LOG_DEBUG(Common_Filesystem, "%s does not exist", filename.c_str());
+ NGLOG_DEBUG(Common_Filesystem, "{} does not exist", filename);
return true;
}
// We can't delete a directory
if (IsDirectory(filename)) {
- LOG_ERROR(Common_Filesystem, "Failed: %s is a directory", filename.c_str());
+ NGLOG_ERROR(Common_Filesystem, "Failed: {} is a directory", filename);
return false;
}
#ifdef _WIN32
if (!DeleteFileW(Common::UTF8ToUTF16W(filename).c_str())) {
- LOG_ERROR(Common_Filesystem, "DeleteFile failed on %s: %s", filename.c_str(),
- GetLastErrorMsg());
+ NGLOG_ERROR(Common_Filesystem, "DeleteFile failed on {}: {}", filename, GetLastErrorMsg());
return false;
}
#else
if (unlink(filename.c_str()) == -1) {
- LOG_ERROR(Common_Filesystem, "unlink failed on %s: %s", filename.c_str(),
- GetLastErrorMsg());
+ NGLOG_ERROR(Common_Filesystem, "unlink failed on {}: {}", filename, GetLastErrorMsg());
return false;
}
#endif
@@ -162,16 +160,16 @@ bool Delete(const std::string& filename) {
// Returns true if successful, or path already exists.
bool CreateDir(const std::string& path) {
- LOG_TRACE(Common_Filesystem, "directory %s", path.c_str());
+ NGLOG_TRACE(Common_Filesystem, "directory {}", path);
#ifdef _WIN32
if (::CreateDirectoryW(Common::UTF8ToUTF16W(path).c_str(), nullptr))
return true;
DWORD error = GetLastError();
if (error == ERROR_ALREADY_EXISTS) {
- LOG_DEBUG(Common_Filesystem, "CreateDirectory failed on %s: already exists", path.c_str());
+ NGLOG_DEBUG(Common_Filesystem, "CreateDirectory failed on {}: already exists", path);
return true;
}
- LOG_ERROR(Common_Filesystem, "CreateDirectory failed on %s: %i", path.c_str(), error);
+ NGLOG_ERROR(Common_Filesystem, "CreateDirectory failed on {}: {}", path, error);
return false;
#else
if (mkdir(path.c_str(), 0755) == 0)
@@ -180,11 +178,11 @@ bool CreateDir(const std::string& path) {
int err = errno;
if (err == EEXIST) {
- LOG_DEBUG(Common_Filesystem, "mkdir failed on %s: already exists", path.c_str());
+ NGLOG_DEBUG(Common_Filesystem, "mkdir failed on {}: already exists", path);
return true;
}
- LOG_ERROR(Common_Filesystem, "mkdir failed on %s: %s", path.c_str(), strerror(err));
+ NGLOG_ERROR(Common_Filesystem, "mkdir failed on {}: {}", path, strerror(err));
return false;
#endif
}
@@ -192,10 +190,10 @@ bool CreateDir(const std::string& path) {
// Creates the full path of fullPath returns true on success
bool CreateFullPath(const std::string& fullPath) {
int panicCounter = 100;
- LOG_TRACE(Common_Filesystem, "path %s", fullPath.c_str());
+ NGLOG_TRACE(Common_Filesystem, "path {}", fullPath);
if (FileUtil::Exists(fullPath)) {
- LOG_DEBUG(Common_Filesystem, "path exists %s", fullPath.c_str());
+ NGLOG_DEBUG(Common_Filesystem, "path exists {}", fullPath);
return true;
}
@@ -211,14 +209,14 @@ bool CreateFullPath(const std::string& fullPath) {
// Include the '/' so the first call is CreateDir("/") rather than CreateDir("")
std::string const subPath(fullPath.substr(0, position + 1));
if (!FileUtil::IsDirectory(subPath) && !FileUtil::CreateDir(subPath)) {
- LOG_ERROR(Common, "CreateFullPath: directory creation failed");
+ NGLOG_ERROR(Common, "CreateFullPath: directory creation failed");
return false;
}
// A safety check
panicCounter--;
if (panicCounter <= 0) {
- LOG_ERROR(Common, "CreateFullPath: directory structure is too deep");
+ NGLOG_ERROR(Common, "CreateFullPath: directory structure is too deep");
return false;
}
position++;
@@ -227,11 +225,11 @@ 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 %s", filename.c_str());
+ NGLOG_TRACE(Common_Filesystem, "directory {}", filename);
// check if a directory
if (!FileUtil::IsDirectory(filename)) {
- LOG_ERROR(Common_Filesystem, "Not a directory %s", filename.c_str());
+ NGLOG_ERROR(Common_Filesystem, "Not a directory {}", filename);
return false;
}
@@ -242,14 +240,14 @@ bool DeleteDir(const std::string& filename) {
if (rmdir(filename.c_str()) == 0)
return true;
#endif
- LOG_ERROR(Common_Filesystem, "failed %s: %s", filename.c_str(), GetLastErrorMsg());
+ NGLOG_ERROR(Common_Filesystem, "failed {}: {}", filename, GetLastErrorMsg());
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, "%s --> %s", srcFilename.c_str(), destFilename.c_str());
+ NGLOG_TRACE(Common_Filesystem, "{} --> {}", srcFilename, destFilename);
#ifdef _WIN32
if (_wrename(Common::UTF8ToUTF16W(srcFilename).c_str(),
Common::UTF8ToUTF16W(destFilename).c_str()) == 0)
@@ -258,21 +256,21 @@ bool Rename(const std::string& srcFilename, const std::string& destFilename) {
if (rename(srcFilename.c_str(), destFilename.c_str()) == 0)
return true;
#endif
- LOG_ERROR(Common_Filesystem, "failed %s --> %s: %s", srcFilename.c_str(), destFilename.c_str(),
- GetLastErrorMsg());
+ NGLOG_ERROR(Common_Filesystem, "failed {} --> {}: {}", srcFilename, destFilename,
+ GetLastErrorMsg());
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, "%s --> %s", srcFilename.c_str(), destFilename.c_str());
+ NGLOG_TRACE(Common_Filesystem, "{} --> {}", srcFilename, destFilename);
#ifdef _WIN32
if (CopyFileW(Common::UTF8ToUTF16W(srcFilename).c_str(),
Common::UTF8ToUTF16W(destFilename).c_str(), FALSE))
return true;
- LOG_ERROR(Common_Filesystem, "failed %s --> %s: %s", srcFilename.c_str(), destFilename.c_str(),
- GetLastErrorMsg());
+ NGLOG_ERROR(Common_Filesystem, "failed {} --> {}: {}", srcFilename, destFilename,
+ GetLastErrorMsg());
return false;
#else
@@ -284,8 +282,8 @@ bool Copy(const std::string& srcFilename, const std::string& destFilename) {
// Open input file
FILE* input = fopen(srcFilename.c_str(), "rb");
if (!input) {
- LOG_ERROR(Common_Filesystem, "opening input failed %s --> %s: %s", srcFilename.c_str(),
- destFilename.c_str(), GetLastErrorMsg());
+ NGLOG_ERROR(Common_Filesystem, "opening input failed {} --> {}: {}", srcFilename,
+ destFilename, GetLastErrorMsg());
return false;
}
@@ -293,8 +291,8 @@ bool Copy(const std::string& srcFilename, const std::string& destFilename) {
FILE* output = fopen(destFilename.c_str(), "wb");
if (!output) {
fclose(input);
- LOG_ERROR(Common_Filesystem, "opening output failed %s --> %s: %s", srcFilename.c_str(),
- destFilename.c_str(), GetLastErrorMsg());
+ NGLOG_ERROR(Common_Filesystem, "opening output failed {} --> {}: {}", srcFilename,
+ destFilename, GetLastErrorMsg());
return false;
}
@@ -304,8 +302,8 @@ bool Copy(const std::string& srcFilename, const std::string& destFilename) {
size_t rnum = fread(buffer, sizeof(char), BSIZE, input);
if (rnum != BSIZE) {
if (ferror(input) != 0) {
- LOG_ERROR(Common_Filesystem, "failed reading from source, %s --> %s: %s",
- srcFilename.c_str(), destFilename.c_str(), GetLastErrorMsg());
+ NGLOG_ERROR(Common_Filesystem, "failed reading from source, {} --> {}: {}",
+ srcFilename, destFilename, GetLastErrorMsg());
goto bail;
}
}
@@ -313,8 +311,8 @@ bool Copy(const std::string& srcFilename, const std::string& destFilename) {
// write output
size_t wnum = fwrite(buffer, sizeof(char), rnum, output);
if (wnum != rnum) {
- LOG_ERROR(Common_Filesystem, "failed writing to output, %s --> %s: %s",
- srcFilename.c_str(), destFilename.c_str(), GetLastErrorMsg());
+ NGLOG_ERROR(Common_Filesystem, "failed writing to output, {} --> {}: {}", srcFilename,
+ destFilename, GetLastErrorMsg());
goto bail;
}
}
@@ -334,12 +332,12 @@ bail:
// Returns the size of filename (64bit)
u64 GetSize(const std::string& filename) {
if (!Exists(filename)) {
- LOG_ERROR(Common_Filesystem, "failed %s: No such file", filename.c_str());
+ NGLOG_ERROR(Common_Filesystem, "failed {}: No such file", filename);
return 0;
}
if (IsDirectory(filename)) {
- LOG_ERROR(Common_Filesystem, "failed %s: is a directory", filename.c_str());
+ NGLOG_ERROR(Common_Filesystem, "failed {}: is a directory", filename);
return 0;
}
@@ -350,11 +348,11 @@ u64 GetSize(const std::string& filename) {
if (stat(filename.c_str(), &buf) == 0)
#endif
{
- LOG_TRACE(Common_Filesystem, "%s: %lld", filename.c_str(), (long long)buf.st_size);
+ NGLOG_TRACE(Common_Filesystem, "{}: {}", filename, buf.st_size);
return buf.st_size;
}
- LOG_ERROR(Common_Filesystem, "Stat failed %s: %s", filename.c_str(), GetLastErrorMsg());
+ NGLOG_ERROR(Common_Filesystem, "Stat failed {}: {}", filename, GetLastErrorMsg());
return 0;
}
@@ -362,7 +360,7 @@ u64 GetSize(const std::string& filename) {
u64 GetSize(const int fd) {
struct stat buf;
if (fstat(fd, &buf) != 0) {
- LOG_ERROR(Common_Filesystem, "GetSize: stat failed %i: %s", fd, GetLastErrorMsg());
+ NGLOG_ERROR(Common_Filesystem, "GetSize: stat failed {}: {}", fd, GetLastErrorMsg());
return 0;
}
return buf.st_size;
@@ -373,12 +371,14 @@ u64 GetSize(FILE* f) {
// can't use off_t here because it can be 32-bit
u64 pos = ftello(f);
if (fseeko(f, 0, SEEK_END) != 0) {
- LOG_ERROR(Common_Filesystem, "GetSize: seek failed %p: %s", f, GetLastErrorMsg());
+ NGLOG_ERROR(Common_Filesystem, "GetSize: seek failed {}: {}", fmt::ptr(f),
+ GetLastErrorMsg());
return 0;
}
u64 size = ftello(f);
if ((size != pos) && (fseeko(f, pos, SEEK_SET) != 0)) {
- LOG_ERROR(Common_Filesystem, "GetSize: seek failed %p: %s", f, GetLastErrorMsg());
+ NGLOG_ERROR(Common_Filesystem, "GetSize: seek failed {}: {}", fmt::ptr(f),
+ GetLastErrorMsg());
return 0;
}
return size;
@@ -386,10 +386,10 @@ u64 GetSize(FILE* f) {
// creates an empty file filename, returns true on success
bool CreateEmptyFile(const std::string& filename) {
- LOG_TRACE(Common_Filesystem, "%s", filename.c_str());
+ NGLOG_TRACE(Common_Filesystem, "{}", filename);
if (!FileUtil::IOFile(filename, "wb")) {
- LOG_ERROR(Common_Filesystem, "failed %s: %s", filename.c_str(), GetLastErrorMsg());
+ NGLOG_ERROR(Common_Filesystem, "failed {}: {}", filename, GetLastErrorMsg());
return false;
}
@@ -398,7 +398,7 @@ bool CreateEmptyFile(const std::string& filename) {
bool ForeachDirectoryEntry(unsigned* num_entries_out, const std::string& directory,
DirectoryEntryCallable callback) {
- LOG_TRACE(Common_Filesystem, "directory %s", directory.c_str());
+ NGLOG_TRACE(Common_Filesystem, "directory {}", directory);
// How many files + directories we found
unsigned found_entries = 0;
@@ -556,7 +556,7 @@ std::string GetCurrentDir() {
char* dir;
if (!(dir = getcwd(nullptr, 0))) {
#endif
- LOG_ERROR(Common_Filesystem, "GetCurrentDirectory failed: %s", GetLastErrorMsg());
+ NGLOG_ERROR(Common_Filesystem, "GetCurrentDirectory failed: {}", GetLastErrorMsg());
return nullptr;
}
#ifdef _WIN32
@@ -653,12 +653,12 @@ static const std::string GetUserDirectory(const std::string& envvar) {
else if (envvar == "XDG_CACHE_HOME")
subdirectory = DIR_SEP ".cache";
else
- ASSERT_MSG(false, "Unknown XDG variable %s.", envvar.c_str());
+ ASSERT_MSG(false, "Unknown XDG variable {}.", envvar);
user_dir = GetHomeDirectory() + subdirectory;
}
- ASSERT_MSG(!user_dir.empty(), "User directory %s musn’t be empty.", envvar.c_str());
- ASSERT_MSG(user_dir[0] == '/', "User directory %s must be absolute.", envvar.c_str());
+ ASSERT_MSG(!user_dir.empty(), "User directory {} mustn’t be empty.", envvar);
+ ASSERT_MSG(user_dir[0] == '/', "User directory {} must be absolute.", envvar);
return user_dir;
}
@@ -676,7 +676,7 @@ std::string GetSysDirectory() {
#endif
sysDir += DIR_SEP;
- LOG_DEBUG(Common_Filesystem, "Setting to %s:", sysDir.c_str());
+ NGLOG_DEBUG(Common_Filesystem, "Setting to {}:", sysDir);
return sysDir;
}
@@ -692,7 +692,7 @@ const std::string& GetUserPath(const unsigned int DirIDX, const std::string& new
if (!FileUtil::IsDirectory(paths[D_USER_IDX])) {
paths[D_USER_IDX] = AppDataRoamingDirectory() + DIR_SEP EMU_DATA_DIR DIR_SEP;
} else {
- LOG_INFO(Common_Filesystem, "Using the local user directory");
+ NGLOG_INFO(Common_Filesystem, "Using the local user directory");
}
paths[D_CONFIG_IDX] = paths[D_USER_IDX] + CONFIG_DIR DIR_SEP;
@@ -719,7 +719,7 @@ const std::string& GetUserPath(const unsigned int DirIDX, const std::string& new
if (!newPath.empty()) {
if (!FileUtil::IsDirectory(newPath)) {
- LOG_ERROR(Common_Filesystem, "Invalid path specified %s", newPath.c_str());
+ NGLOG_ERROR(Common_Filesystem, "Invalid path specified {}", newPath);
return paths[DirIDX];
} else {
paths[DirIDX] = newPath;
@@ -809,16 +809,16 @@ IOFile::~IOFile() {
Close();
}
-IOFile::IOFile(IOFile&& other) {
+IOFile::IOFile(IOFile&& other) noexcept {
Swap(other);
}
-IOFile& IOFile::operator=(IOFile&& other) {
+IOFile& IOFile::operator=(IOFile&& other) noexcept {
Swap(other);
return *this;
}
-void IOFile::Swap(IOFile& other) {
+void IOFile::Swap(IOFile& other) noexcept {
std::swap(m_file, other.m_file);
std::swap(m_good, other.m_good);
}
diff --git a/src/common/file_util.h b/src/common/file_util.h
index 630232a25..fc6b3ea46 100644
--- a/src/common/file_util.h
+++ b/src/common/file_util.h
@@ -121,7 +121,7 @@ void CopyDir(const std::string& source_path, const std::string& dest_path);
// Set the current directory to given directory
bool SetCurrentDir(const std::string& directory);
-// Returns a pointer to a string with a Citra data dir in the user's home
+// Returns a pointer to a string with a yuzu data dir in the user's home
// directory. To be used in "multi-user" mode (that is, installed).
const std::string& GetUserPath(const unsigned int DirIDX, const std::string& newPath = "");
@@ -160,22 +160,18 @@ public:
~IOFile();
- IOFile(IOFile&& other);
- IOFile& operator=(IOFile&& other);
+ IOFile(IOFile&& other) noexcept;
+ IOFile& operator=(IOFile&& other) noexcept;
- void Swap(IOFile& other);
+ void Swap(IOFile& other) noexcept;
bool Open(const std::string& filename, const char openmode[]);
bool Close();
template <typename T>
size_t ReadArray(T* data, size_t length) {
- static_assert(std::is_standard_layout<T>(),
- "Given array does not consist of standard layout objects");
-#if (__GNUC__ >= 5) || defined(__clang__) || defined(_MSC_VER)
static_assert(std::is_trivially_copyable<T>(),
"Given array does not consist of trivially copyable objects");
-#endif
if (!IsOpen()) {
m_good = false;
@@ -191,12 +187,8 @@ public:
template <typename T>
size_t WriteArray(const T* data, size_t length) {
- static_assert(std::is_standard_layout<T>(),
- "Given array does not consist of standard layout objects");
-#if (__GNUC__ >= 5) || defined(__clang__) || defined(_MSC_VER)
static_assert(std::is_trivially_copyable<T>(),
"Given array does not consist of trivially copyable objects");
-#endif
if (!IsOpen()) {
m_good = false;
@@ -210,11 +202,15 @@ public:
return items_written;
}
- size_t ReadBytes(void* data, size_t length) {
+ template <typename T>
+ size_t ReadBytes(T* data, size_t length) {
+ static_assert(std::is_trivially_copyable<T>(), "T must be trivially copyable");
return ReadArray(reinterpret_cast<char*>(data), length);
}
- size_t WriteBytes(const void* data, size_t length) {
+ template <typename T>
+ size_t WriteBytes(const T* data, size_t length) {
+ static_assert(std::is_trivially_copyable<T>(), "T must be trivially copyable");
return WriteArray(reinterpret_cast<const char*>(data), length);
}
diff --git a/src/common/hash.cpp b/src/common/hash.cpp
deleted file mode 100644
index a02e9e5b9..000000000
--- a/src/common/hash.cpp
+++ /dev/null
@@ -1,141 +0,0 @@
-// Copyright 2015 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#if defined(_MSC_VER)
-#include <stdlib.h>
-#endif
-#include "common/common_funcs.h"
-#include "common/common_types.h"
-#include "common/hash.h"
-
-namespace Common {
-
-// MurmurHash3 was written by Austin Appleby, and is placed in the public
-// domain. The author hereby disclaims copyright to this source code.
-
-// Block read - if your platform needs to do endian-swapping or can only handle aligned reads, do
-// the conversion here
-static FORCE_INLINE u64 getblock64(const u64* p, size_t i) {
- return p[i];
-}
-
-// Finalization mix - force all bits of a hash block to avalanche
-static FORCE_INLINE u64 fmix64(u64 k) {
- k ^= k >> 33;
- k *= 0xff51afd7ed558ccdllu;
- k ^= k >> 33;
- k *= 0xc4ceb9fe1a85ec53llu;
- k ^= k >> 33;
-
- return k;
-}
-
-// This is the 128-bit variant of the MurmurHash3 hash function that is targeted for 64-bit
-// platforms (MurmurHash3_x64_128). It was taken from:
-// https://code.google.com/p/smhasher/source/browse/trunk/MurmurHash3.cpp
-void MurmurHash3_128(const void* key, size_t len, u32 seed, void* out) {
- const u8* data = (const u8*)key;
- const size_t nblocks = len / 16;
-
- u64 h1 = seed;
- u64 h2 = seed;
-
- const u64 c1 = 0x87c37b91114253d5llu;
- const u64 c2 = 0x4cf5ad432745937fllu;
-
- // Body
-
- const u64* blocks = (const u64*)(data);
-
- for (size_t i = 0; i < nblocks; i++) {
- u64 k1 = getblock64(blocks, i * 2 + 0);
- u64 k2 = getblock64(blocks, i * 2 + 1);
-
- k1 *= c1;
- k1 = _rotl64(k1, 31);
- k1 *= c2;
- h1 ^= k1;
-
- h1 = _rotl64(h1, 27);
- h1 += h2;
- h1 = h1 * 5 + 0x52dce729;
-
- k2 *= c2;
- k2 = _rotl64(k2, 33);
- k2 *= c1;
- h2 ^= k2;
-
- h2 = _rotl64(h2, 31);
- h2 += h1;
- h2 = h2 * 5 + 0x38495ab5;
- }
-
- // Tail
-
- const u8* tail = (const u8*)(data + nblocks * 16);
-
- u64 k1 = 0;
- u64 k2 = 0;
-
- switch (len & 15) {
- case 15:
- k2 ^= ((u64)tail[14]) << 48;
- case 14:
- k2 ^= ((u64)tail[13]) << 40;
- case 13:
- k2 ^= ((u64)tail[12]) << 32;
- case 12:
- k2 ^= ((u64)tail[11]) << 24;
- case 11:
- k2 ^= ((u64)tail[10]) << 16;
- case 10:
- k2 ^= ((u64)tail[9]) << 8;
- case 9:
- k2 ^= ((u64)tail[8]) << 0;
- k2 *= c2;
- k2 = _rotl64(k2, 33);
- k2 *= c1;
- h2 ^= k2;
-
- case 8:
- k1 ^= ((u64)tail[7]) << 56;
- case 7:
- k1 ^= ((u64)tail[6]) << 48;
- case 6:
- k1 ^= ((u64)tail[5]) << 40;
- case 5:
- k1 ^= ((u64)tail[4]) << 32;
- case 4:
- k1 ^= ((u64)tail[3]) << 24;
- case 3:
- k1 ^= ((u64)tail[2]) << 16;
- case 2:
- k1 ^= ((u64)tail[1]) << 8;
- case 1:
- k1 ^= ((u64)tail[0]) << 0;
- k1 *= c1;
- k1 = _rotl64(k1, 31);
- k1 *= c2;
- h1 ^= k1;
- };
-
- // Finalization
-
- h1 ^= len;
- h2 ^= len;
-
- h1 += h2;
- h2 += h1;
-
- h1 = fmix64(h1);
- h2 = fmix64(h2);
-
- h1 += h2;
- h2 += h1;
-
- ((u64*)out)[0] = h1;
- ((u64*)out)[1] = h2;
-}
-
-} // namespace Common
diff --git a/src/common/hash.h b/src/common/hash.h
index ee2560dad..73c326980 100644
--- a/src/common/hash.h
+++ b/src/common/hash.h
@@ -5,12 +5,12 @@
#pragma once
#include <cstddef>
+#include <cstring>
+#include "common/cityhash.h"
#include "common/common_types.h"
namespace Common {
-void MurmurHash3_128(const void* key, size_t len, u32 seed, void* out);
-
/**
* Computes a 64-bit hash over the specified block of data
* @param data Block of data to compute hash over
@@ -18,9 +18,54 @@ void MurmurHash3_128(const void* key, size_t len, u32 seed, void* out);
* @returns 64-bit hash value that was computed over the data block
*/
static inline u64 ComputeHash64(const void* data, size_t len) {
- u64 res[2];
- MurmurHash3_128(data, len, 0, res);
- return res[0];
+ return CityHash64(static_cast<const char*>(data), len);
+}
+
+/**
+ * Computes a 64-bit hash of a struct. In addition to being trivially copyable, it is also critical
+ * that either the struct includes no padding, or that any padding is initialized to a known value
+ * by memsetting the struct to 0 before filling it in.
+ */
+template <typename T>
+static inline u64 ComputeStructHash64(const T& data) {
+ static_assert(std::is_trivially_copyable<T>(),
+ "Type passed to ComputeStructHash64 must be trivially copyable");
+ return ComputeHash64(&data, sizeof(data));
}
+/// A helper template that ensures the padding in a struct is initialized by memsetting to 0.
+template <typename T>
+struct HashableStruct {
+ // In addition to being trivially copyable, T must also have a trivial default constructor,
+ // because any member initialization would be overridden by memset
+ static_assert(std::is_trivial<T>(), "Type passed to HashableStruct must be trivial");
+ /*
+ * We use a union because "implicitly-defined copy/move constructor for a union X copies the
+ * object representation of X." and "implicitly-defined copy assignment operator for a union X
+ * copies the object representation (3.9) of X." = Bytewise copy instead of memberwise copy.
+ * This is important because the padding bytes are included in the hash and comparison between
+ * objects.
+ */
+ union {
+ T state;
+ };
+
+ HashableStruct() {
+ // Memset structure to zero padding bits, so that they will be deterministic when hashing
+ std::memset(&state, 0, sizeof(T));
+ }
+
+ bool operator==(const HashableStruct<T>& o) const {
+ return std::memcmp(&state, &o.state, sizeof(T)) == 0;
+ };
+
+ bool operator!=(const HashableStruct<T>& o) const {
+ return !(*this == o);
+ };
+
+ size_t Hash() const {
+ return Common::ComputeStructHash64(state);
+ }
+};
+
} // namespace Common
diff --git a/src/common/linear_disk_cache.h b/src/common/linear_disk_cache.h
deleted file mode 100644
index 94c695163..000000000
--- a/src/common/linear_disk_cache.h
+++ /dev/null
@@ -1,167 +0,0 @@
-// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <fstream>
-#include "common/common_types.h"
-
-// defined in Version.cpp
-extern const char* scm_rev_git_str;
-
-// On disk format:
-// header{
-// u32 'DCAC';
-// u32 version; // svn_rev
-// u16 sizeof(key_type);
-// u16 sizeof(value_type);
-//}
-
-// key_value_pair{
-// u32 value_size;
-// key_type key;
-// value_type[value_size] value;
-//}
-
-template <typename K, typename V>
-class LinearDiskCacheReader {
-public:
- virtual void Read(const K& key, const V* value, u32 value_size) = 0;
-};
-
-// Dead simple unsorted key-value store with append functionality.
-// No random read functionality, all reading is done in OpenAndRead.
-// Keys and values can contain any characters, including \0.
-//
-// Suitable for caching generated shader bytecode between executions.
-// Not tuned for extreme performance but should be reasonably fast.
-// Does not support keys or values larger than 2GB, which should be reasonable.
-// Keys must have non-zero length; values can have zero length.
-
-// K and V are some POD type
-// K : the key type
-// V : value array type
-template <typename K, typename V>
-class LinearDiskCache {
-public:
- // return number of read entries
- u32 OpenAndRead(const char* filename, LinearDiskCacheReader<K, V>& reader) {
- using std::ios_base;
-
- // close any currently opened file
- Close();
- m_num_entries = 0;
-
- // try opening for reading/writing
- OpenFStream(m_file, filename, ios_base::in | ios_base::out | ios_base::binary);
-
- m_file.seekg(0, std::ios::end);
- std::fstream::pos_type end_pos = m_file.tellg();
- m_file.seekg(0, std::ios::beg);
- std::fstream::pos_type start_pos = m_file.tellg();
- std::streamoff file_size = end_pos - start_pos;
-
- if (m_file.is_open() && ValidateHeader()) {
- // good header, read some key/value pairs
- K key;
-
- V* value = nullptr;
- u32 value_size;
- u32 entry_number;
-
- std::fstream::pos_type last_pos = m_file.tellg();
-
- while (Read(&value_size)) {
- std::streamoff next_extent =
- (last_pos - start_pos) + sizeof(value_size) + value_size;
- if (next_extent > file_size)
- break;
-
- delete[] value;
- value = new V[value_size];
-
- // read key/value and pass to reader
- if (Read(&key) && Read(value, value_size) && Read(&entry_number) &&
- entry_number == m_num_entries + 1) {
- reader.Read(key, value, value_size);
- } else {
- break;
- }
-
- m_num_entries++;
- last_pos = m_file.tellg();
- }
- m_file.seekp(last_pos);
- m_file.clear();
-
- delete[] value;
- return m_num_entries;
- }
-
- // failed to open file for reading or bad header
- // close and recreate file
- Close();
- m_file.open(filename, ios_base::out | ios_base::trunc | ios_base::binary);
- WriteHeader();
- return 0;
- }
-
- void Sync() {
- m_file.flush();
- }
-
- void Close() {
- if (m_file.is_open())
- m_file.close();
- // clear any error flags
- m_file.clear();
- }
-
- // Appends a key-value pair to the store.
- void Append(const K& key, const V* value, u32 value_size) {
- // TODO: Should do a check that we don't already have "key"? (I think each caller does that
- // already.)
- Write(&value_size);
- Write(&key);
- Write(value, value_size);
- m_num_entries++;
- Write(&m_num_entries);
- }
-
-private:
- void WriteHeader() {
- Write(&m_header);
- }
-
- bool ValidateHeader() {
- char file_header[sizeof(Header)];
-
- return (Read(file_header, sizeof(Header)) &&
- !memcmp((const char*)&m_header, file_header, sizeof(Header)));
- }
-
- template <typename D>
- bool Write(const D* data, u32 count = 1) {
- return m_file.write((const char*)data, count * sizeof(D)).good();
- }
-
- template <typename D>
- bool Read(const D* data, u32 count = 1) {
- return m_file.read((char*)data, count * sizeof(D)).good();
- }
-
- struct Header {
- Header() : id(*(u32*)"DCAC"), key_t_size(sizeof(K)), value_t_size(sizeof(V)) {
- memcpy(ver, scm_rev_git_str, 40);
- }
-
- const u32 id;
- const u16 key_t_size, value_t_size;
- char ver[40];
-
- } m_header;
-
- std::fstream m_file;
- u32 m_num_entries;
-};
diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp
index 7f3ae1a4e..c26b20062 100644
--- a/src/common/logging/backend.cpp
+++ b/src/common/logging/backend.cpp
@@ -2,15 +2,13 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include <algorithm>
-#include <array>
-#include <cstdio>
+#include <utility>
#include "common/assert.h"
-#include "common/common_funcs.h" // snprintf compatibility define
#include "common/logging/backend.h"
#include "common/logging/filter.h"
#include "common/logging/log.h"
#include "common/logging/text_formatter.h"
+#include "common/string_util.h"
namespace Log {
@@ -37,16 +35,23 @@ namespace Log {
SUB(Service, AM) \
SUB(Service, AOC) \
SUB(Service, APM) \
+ SUB(Service, BCAT) \
+ SUB(Service, Fatal) \
SUB(Service, Friend) \
SUB(Service, FS) \
SUB(Service, HID) \
SUB(Service, LM) \
+ SUB(Service, MM) \
+ SUB(Service, NFP) \
SUB(Service, NIFM) \
SUB(Service, NS) \
SUB(Service, NVDRV) \
SUB(Service, PCTL) \
+ SUB(Service, PREPO) \
SUB(Service, SET) \
SUB(Service, SM) \
+ SUB(Service, SPL) \
+ SUB(Service, SSL) \
SUB(Service, Time) \
SUB(Service, VI) \
CLS(HW) \
@@ -102,25 +107,20 @@ const char* GetLevelName(Level log_level) {
}
Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsigned int line_nr,
- const char* function, const char* format, va_list args) {
+ const char* function, std::string message) {
using std::chrono::duration_cast;
using std::chrono::steady_clock;
static steady_clock::time_point time_origin = steady_clock::now();
- std::array<char, 4 * 1024> formatting_buffer;
-
Entry entry;
entry.timestamp = duration_cast<std::chrono::microseconds>(steady_clock::now() - time_origin);
entry.log_class = log_class;
entry.log_level = log_level;
-
- snprintf(formatting_buffer.data(), formatting_buffer.size(), "%s:%s:%u", filename, function,
- line_nr);
- entry.location = std::string(formatting_buffer.data());
-
- vsnprintf(formatting_buffer.data(), formatting_buffer.size(), format, args);
- entry.message = std::string(formatting_buffer.data());
+ entry.filename = Common::TrimSourcePath(filename);
+ entry.line_num = line_nr;
+ entry.function = function;
+ entry.message = std::move(message);
return entry;
}
@@ -131,15 +131,13 @@ void SetFilter(Filter* new_filter) {
filter = new_filter;
}
-void LogMessage(Class log_class, Level log_level, const char* filename, unsigned int line_nr,
- const char* function, const char* format, ...) {
- if (filter != nullptr && !filter->CheckMessage(log_class, log_level))
+void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename,
+ unsigned int line_num, const char* function, const char* format,
+ const fmt::format_args& args) {
+ if (filter && !filter->CheckMessage(log_class, log_level))
return;
-
- va_list args;
- va_start(args, format);
- Entry entry = CreateEntry(log_class, log_level, filename, line_nr, function, format, args);
- va_end(args);
+ Entry entry =
+ CreateEntry(log_class, log_level, filename, line_num, function, fmt::vformat(format, args));
PrintColoredMessage(entry);
}
diff --git a/src/common/logging/backend.h b/src/common/logging/backend.h
index 70744e3e5..7e81efb23 100644
--- a/src/common/logging/backend.h
+++ b/src/common/logging/backend.h
@@ -22,13 +22,16 @@ struct Entry {
std::chrono::microseconds timestamp;
Class log_class;
Level log_level;
- std::string location;
+ std::string filename;
+ unsigned int line_num;
+ std::string function;
std::string message;
Entry() = default;
Entry(Entry&& o) = default;
Entry& operator=(Entry&& o) = default;
+ Entry& operator=(const Entry& o) = default;
};
/**
@@ -44,7 +47,7 @@ const char* GetLevelName(Level log_level);
/// Creates a log entry by formatting the given source location, and message.
Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsigned int line_nr,
- const char* function, const char* format, va_list args);
+ const char* function, std::string message);
void SetFilter(Filter* filter);
} // namespace Log
diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp
index 733247b51..428723dce 100644
--- a/src/common/logging/filter.cpp
+++ b/src/common/logging/filter.cpp
@@ -65,14 +65,14 @@ bool Filter::ParseFilterRule(const std::string::const_iterator begin,
const std::string::const_iterator end) {
auto level_separator = std::find(begin, end, ':');
if (level_separator == end) {
- LOG_ERROR(Log, "Invalid log filter. Must specify a log level after `:`: %s",
- std::string(begin, end).c_str());
+ NGLOG_ERROR(Log, "Invalid log filter. Must specify a log level after `:`: %s",
+ std::string(begin, end).c_str());
return false;
}
const Level level = GetLevelByName(level_separator + 1, end);
if (level == Level::Count) {
- LOG_ERROR(Log, "Unknown log level in filter: %s", std::string(begin, end).c_str());
+ NGLOG_ERROR(Log, "Unknown log level in filter: %s", std::string(begin, end).c_str());
return false;
}
@@ -83,7 +83,7 @@ bool Filter::ParseFilterRule(const std::string::const_iterator begin,
const Class log_class = GetClassByName(begin, level_separator);
if (log_class == Class::Count) {
- LOG_ERROR(Log, "Unknown log class in filter: %s", std::string(begin, end).c_str());
+ NGLOG_ERROR(Log, "Unknown log class in filter: %s", std::string(begin, end).c_str());
return false;
}
diff --git a/src/common/logging/filter.h b/src/common/logging/filter.h
index 16fa72642..ccca289bd 100644
--- a/src/common/logging/filter.h
+++ b/src/common/logging/filter.h
@@ -19,7 +19,7 @@ namespace Log {
class Filter {
public:
/// Initializes the filter with all classes having `default_level` as the minimum level.
- Filter(Level default_level);
+ Filter(Level default_level = Level::Info);
/// Resets the filter so that all classes have `level` as the minimum displayed level.
void ResetAll(Level level);
diff --git a/src/common/logging/log.h b/src/common/logging/log.h
index 3cf13fcb0..c5015531c 100644
--- a/src/common/logging/log.h
+++ b/src/common/logging/log.h
@@ -4,6 +4,7 @@
#pragma once
+#include <fmt/format.h>
#include "common/common_types.h"
namespace Log {
@@ -54,16 +55,23 @@ enum class Class : ClassType {
Service_AOC, ///< The AOC (AddOn Content) service
Service_APM, ///< The APM (Performance) service
Service_Audio, ///< The Audio (Audio control) service
+ Service_BCAT, ///< The BCAT service
+ Service_Fatal, ///< The Fatal service
Service_Friend, ///< The friend service
Service_FS, ///< The FS (Filesystem) service
Service_HID, ///< The HID (Human interface device) service
Service_LM, ///< The LM (Logger) service
+ Service_MM, ///< The MM (Multimedia) service
+ Service_NFP, ///< The NFP service
Service_NIFM, ///< The NIFM (Network interface) service
Service_NS, ///< The NS services
Service_NVDRV, ///< The NVDRV (Nvidia driver) service
Service_PCTL, ///< The PCTL (Parental control) service
+ Service_PREPO, ///< The PREPO (Play report) service
Service_SET, ///< The SET (Settings) service
Service_SM, ///< The SM (Service manager) service
+ Service_SPL, ///< The SPL service
+ Service_SSL, ///< The SSL service
Service_Time, ///< The time service
Service_VI, ///< The VI (Video interface) service
HW, ///< Low-level hardware emulation
@@ -82,42 +90,44 @@ enum class Class : ClassType {
Loader, ///< ROM loader
Input, ///< Input emulation
Network, ///< Network emulation
- WebService, ///< Interface to Citra Web Services
+ WebService, ///< Interface to yuzu Web Services
Count ///< Total number of logging classes
};
-/// Logs a message to the global logger.
-void LogMessage(Class log_class, Level log_level, const char* filename, unsigned int line_nr,
- const char* function,
-#ifdef _MSC_VER
- _Printf_format_string_
-#endif
- const char* format,
- ...)
-#ifdef __GNUC__
- __attribute__((format(printf, 6, 7)))
-#endif
- ;
+/// Logs a message to the global logger, using fmt
+void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename,
+ unsigned int line_num, const char* function, const char* format,
+ const fmt::format_args& args);
-} // namespace Log
+template <typename... Args>
+void FmtLogMessage(Class log_class, Level log_level, const char* filename, unsigned int line_num,
+ const char* function, const char* format, const Args&... args) {
+ FmtLogMessageImpl(log_class, log_level, filename, line_num, function, format,
+ fmt::make_args(args...));
+}
-#define LOG_GENERIC(log_class, log_level, ...) \
- ::Log::LogMessage(log_class, log_level, __FILE__, __LINE__, __func__, __VA_ARGS__)
+} // namespace Log
#ifdef _DEBUG
-#define LOG_TRACE(log_class, ...) \
- LOG_GENERIC(::Log::Class::log_class, ::Log::Level::Trace, __VA_ARGS__)
+#define NGLOG_TRACE(log_class, ...) \
+ ::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Trace, __FILE__, __LINE__, \
+ __func__, __VA_ARGS__)
#else
-#define LOG_TRACE(log_class, ...) (void(0))
+#define NGLOG_TRACE(log_class, fmt, ...) (void(0))
#endif
-#define LOG_DEBUG(log_class, ...) \
- LOG_GENERIC(::Log::Class::log_class, ::Log::Level::Debug, __VA_ARGS__)
-#define LOG_INFO(log_class, ...) \
- LOG_GENERIC(::Log::Class::log_class, ::Log::Level::Info, __VA_ARGS__)
-#define LOG_WARNING(log_class, ...) \
- LOG_GENERIC(::Log::Class::log_class, ::Log::Level::Warning, __VA_ARGS__)
-#define LOG_ERROR(log_class, ...) \
- LOG_GENERIC(::Log::Class::log_class, ::Log::Level::Error, __VA_ARGS__)
-#define LOG_CRITICAL(log_class, ...) \
- LOG_GENERIC(::Log::Class::log_class, ::Log::Level::Critical, __VA_ARGS__)
+#define NGLOG_DEBUG(log_class, ...) \
+ ::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Debug, __FILE__, __LINE__, \
+ __func__, __VA_ARGS__)
+#define NGLOG_INFO(log_class, ...) \
+ ::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Info, __FILE__, __LINE__, \
+ __func__, __VA_ARGS__)
+#define NGLOG_WARNING(log_class, ...) \
+ ::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Warning, __FILE__, __LINE__, \
+ __func__, __VA_ARGS__)
+#define NGLOG_ERROR(log_class, ...) \
+ ::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Error, __FILE__, __LINE__, \
+ __func__, __VA_ARGS__)
+#define NGLOG_CRITICAL(log_class, ...) \
+ ::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Critical, __FILE__, __LINE__, \
+ __func__, __VA_ARGS__)
diff --git a/src/common/logging/text_formatter.cpp b/src/common/logging/text_formatter.cpp
index e7e46c76b..8583916a8 100644
--- a/src/common/logging/text_formatter.cpp
+++ b/src/common/logging/text_formatter.cpp
@@ -18,50 +18,29 @@
namespace Log {
-// TODO(bunnei): This should be moved to a generic path manipulation library
-const char* TrimSourcePath(const char* path, const char* root) {
- const char* p = path;
-
- while (*p != '\0') {
- const char* next_slash = p;
- while (*next_slash != '\0' && *next_slash != '/' && *next_slash != '\\') {
- ++next_slash;
- }
-
- bool is_src = Common::ComparePartialString(p, next_slash, root);
- p = next_slash;
-
- if (*p != '\0') {
- ++p;
- }
- if (is_src) {
- path = p;
- }
- }
- return path;
-}
-
-void FormatLogMessage(const Entry& entry, char* out_text, size_t text_len) {
+std::string FormatLogMessage(const Entry& entry) {
unsigned int time_seconds = static_cast<unsigned int>(entry.timestamp.count() / 1000000);
unsigned int time_fractional = static_cast<unsigned int>(entry.timestamp.count() % 1000000);
const char* class_name = GetLogClassName(entry.log_class);
const char* level_name = GetLevelName(entry.log_level);
- snprintf(out_text, text_len, "[%4u.%06u] %s <%s> %s: %s", time_seconds, time_fractional,
- class_name, level_name, TrimSourcePath(entry.location.c_str()), entry.message.c_str());
+ return fmt::format("[{:4d}.{:06d}] {} <{}> {}:{}:{}: {}", time_seconds, time_fractional,
+ class_name, level_name, entry.filename, entry.function, entry.line_num,
+ entry.message);
}
void PrintMessage(const Entry& entry) {
- std::array<char, 4 * 1024> format_buffer;
- FormatLogMessage(entry, format_buffer.data(), format_buffer.size());
- fputs(format_buffer.data(), stderr);
- fputc('\n', stderr);
+ auto str = FormatLogMessage(entry) + '\n';
+ fputs(str.c_str(), stderr);
}
void PrintColoredMessage(const Entry& entry) {
#ifdef _WIN32
- static HANDLE console_handle = GetStdHandle(STD_ERROR_HANDLE);
+ HANDLE console_handle = GetStdHandle(STD_ERROR_HANDLE);
+ if (console_handle == INVALID_HANDLE_VALUE) {
+ return;
+ }
CONSOLE_SCREEN_BUFFER_INFO original_info = {0};
GetConsoleScreenBufferInfo(console_handle, &original_info);
diff --git a/src/common/logging/text_formatter.h b/src/common/logging/text_formatter.h
index 66e86d9ec..c587faefb 100644
--- a/src/common/logging/text_formatter.h
+++ b/src/common/logging/text_formatter.h
@@ -10,20 +10,8 @@ namespace Log {
struct Entry;
-/**
- * Attempts to trim an arbitrary prefix from `path`, leaving only the part starting at `root`. It's
- * intended to be used to strip a system-specific build directory from the `__FILE__` macro,
- * leaving only the path relative to the sources root.
- *
- * @param path The input file path as a null-terminated string
- * @param root The name of the root source directory as a null-terminated string. Path up to and
- * including the last occurrence of this name will be stripped
- * @return A pointer to the same string passed as `path`, but starting at the trimmed portion
- */
-const char* TrimSourcePath(const char* path, const char* root = "src");
-
/// Formats a log entry into the provided text buffer.
-void FormatLogMessage(const Entry& entry, char* out_text, size_t text_len);
+std::string FormatLogMessage(const Entry& entry);
/// Formats and prints a log entry to stderr.
void PrintMessage(const Entry& entry);
/// Prints the same message as `PrintMessage`, but colored acoording to the severity level.
diff --git a/src/common/math_util.h b/src/common/math_util.h
index 45a1ed367..c6a83c953 100644
--- a/src/common/math_util.h
+++ b/src/common/math_util.h
@@ -17,11 +17,6 @@ inline bool IntervalsIntersect(unsigned start0, unsigned length0, unsigned start
return (std::max(start0, start1) < std::min(start0 + length0, start1 + length1));
}
-template <typename T>
-inline T Clamp(const T val, const T& min, const T& max) {
- return std::max(min, std::min(max, val));
-}
-
template <class T>
struct Rectangle {
T left;
diff --git a/src/common/memory_util.cpp b/src/common/memory_util.cpp
index 759ad02ca..4d1ec8fb9 100644
--- a/src/common/memory_util.cpp
+++ b/src/common/memory_util.cpp
@@ -55,7 +55,7 @@ void* AllocateExecutableMemory(size_t size, bool low) {
if (ptr == MAP_FAILED) {
ptr = nullptr;
#endif
- LOG_ERROR(Common_Memory, "Failed to allocate executable memory");
+ NGLOG_ERROR(Common_Memory, "Failed to allocate executable memory");
}
#if !defined(_WIN32) && defined(ARCHITECTURE_X64) && !defined(MAP_32BIT)
else {
@@ -68,7 +68,7 @@ void* AllocateExecutableMemory(size_t size, bool low) {
#if EMU_ARCH_BITS == 64
if ((u64)ptr >= 0x80000000 && low == true)
- LOG_ERROR(Common_Memory, "Executable memory ended up above 2GB!");
+ NGLOG_ERROR(Common_Memory, "Executable memory ended up above 2GB!");
#endif
return ptr;
@@ -85,7 +85,7 @@ void* AllocateMemoryPages(size_t size) {
#endif
if (ptr == nullptr)
- LOG_ERROR(Common_Memory, "Failed to allocate raw memory");
+ NGLOG_ERROR(Common_Memory, "Failed to allocate raw memory");
return ptr;
}
@@ -99,12 +99,12 @@ void* AllocateAlignedMemory(size_t size, size_t alignment) {
ptr = memalign(alignment, size);
#else
if (posix_memalign(&ptr, alignment, size) != 0)
- LOG_ERROR(Common_Memory, "Failed to allocate aligned memory");
+ NGLOG_ERROR(Common_Memory, "Failed to allocate aligned memory");
#endif
#endif
if (ptr == nullptr)
- LOG_ERROR(Common_Memory, "Failed to allocate aligned memory");
+ NGLOG_ERROR(Common_Memory, "Failed to allocate aligned memory");
return ptr;
}
@@ -113,7 +113,7 @@ void FreeMemoryPages(void* ptr, size_t size) {
if (ptr) {
#ifdef _WIN32
if (!VirtualFree(ptr, 0, MEM_RELEASE))
- LOG_ERROR(Common_Memory, "FreeMemoryPages failed!\n%s", GetLastErrorMsg());
+ NGLOG_ERROR(Common_Memory, "FreeMemoryPages failed!\n{}", GetLastErrorMsg());
#else
munmap(ptr, size);
#endif
@@ -134,7 +134,7 @@ void WriteProtectMemory(void* ptr, size_t size, bool allowExecute) {
#ifdef _WIN32
DWORD oldValue;
if (!VirtualProtect(ptr, size, allowExecute ? PAGE_EXECUTE_READ : PAGE_READONLY, &oldValue))
- LOG_ERROR(Common_Memory, "WriteProtectMemory failed!\n%s", GetLastErrorMsg());
+ NGLOG_ERROR(Common_Memory, "WriteProtectMemory failed!\n{}", GetLastErrorMsg());
#else
mprotect(ptr, size, allowExecute ? (PROT_READ | PROT_EXEC) : PROT_READ);
#endif
@@ -145,7 +145,7 @@ void UnWriteProtectMemory(void* ptr, size_t size, bool allowExecute) {
DWORD oldValue;
if (!VirtualProtect(ptr, size, allowExecute ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE,
&oldValue))
- LOG_ERROR(Common_Memory, "UnWriteProtectMemory failed!\n%s", GetLastErrorMsg());
+ NGLOG_ERROR(Common_Memory, "UnWriteProtectMemory failed!\n{}", GetLastErrorMsg());
#else
mprotect(ptr, size,
allowExecute ? (PROT_READ | PROT_WRITE | PROT_EXEC) : PROT_WRITE | PROT_READ);
@@ -167,8 +167,7 @@ std::string MemUsage() {
return "MemUsage Error";
if (GetProcessMemoryInfo(hProcess, &pmc, sizeof(pmc)))
- Ret = Common::StringFromFormat(
- "%s K", Common::ThousandSeparate(pmc.WorkingSetSize / 1024, 7).c_str());
+ Ret = fmt::format("{} K", Common::ThousandSeparate(pmc.WorkingSetSize / 1024, 7));
CloseHandle(hProcess);
return Ret;
diff --git a/src/common/param_package.cpp b/src/common/param_package.cpp
index 3a6ef8c27..ab0154133 100644
--- a/src/common/param_package.cpp
+++ b/src/common/param_package.cpp
@@ -25,7 +25,7 @@ ParamPackage::ParamPackage(const std::string& serialized) {
std::vector<std::string> key_value;
Common::SplitString(pair, KEY_VALUE_SEPARATOR, key_value);
if (key_value.size() != 2) {
- LOG_ERROR(Common, "invalid key pair %s", pair.c_str());
+ NGLOG_ERROR(Common, "invalid key pair {}", pair);
continue;
}
@@ -64,7 +64,7 @@ std::string ParamPackage::Serialize() const {
std::string ParamPackage::Get(const std::string& key, const std::string& default_value) const {
auto pair = data.find(key);
if (pair == data.end()) {
- LOG_DEBUG(Common, "key %s not found", key.c_str());
+ NGLOG_DEBUG(Common, "key '{}' not found", key);
return default_value;
}
@@ -74,14 +74,14 @@ std::string ParamPackage::Get(const std::string& key, const std::string& default
int ParamPackage::Get(const std::string& key, int default_value) const {
auto pair = data.find(key);
if (pair == data.end()) {
- LOG_DEBUG(Common, "key %s not found", key.c_str());
+ NGLOG_DEBUG(Common, "key '{}' not found", key);
return default_value;
}
try {
return std::stoi(pair->second);
} catch (const std::logic_error&) {
- LOG_ERROR(Common, "failed to convert %s to int", pair->second.c_str());
+ NGLOG_ERROR(Common, "failed to convert {} to int", pair->second);
return default_value;
}
}
@@ -89,14 +89,14 @@ int ParamPackage::Get(const std::string& key, int default_value) const {
float ParamPackage::Get(const std::string& key, float default_value) const {
auto pair = data.find(key);
if (pair == data.end()) {
- LOG_DEBUG(Common, "key %s not found", key.c_str());
+ NGLOG_DEBUG(Common, "key {} not found", key);
return default_value;
}
try {
return std::stof(pair->second);
} catch (const std::logic_error&) {
- LOG_ERROR(Common, "failed to convert %s to float", pair->second.c_str());
+ NGLOG_ERROR(Common, "failed to convert {} to float", pair->second);
return default_value;
}
}
diff --git a/src/common/platform.h b/src/common/platform.h
deleted file mode 100644
index c62fb7c8f..000000000
--- a/src/common/platform.h
+++ /dev/null
@@ -1,34 +0,0 @@
-/**
- * Copyright (C) 2005-2012 Gekko Emulator
- *
- * @file platform.h
- * @author ShizZy <shizzy247@gmail.com>
- * @date 2012-02-11
- * @brief Platform detection macros for portable compilation
- *
- * @section LICENSE
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details at
- * http://www.gnu.org/copyleft/gpl.html
- *
- * Official project repository can be found at:
- * http://code.google.com/p/gekko-gc-emu/
- */
-
-#pragma once
-
-////////////////////////////////////////////////////////////////////////////////////////////////////
-// Platform detection
-
-#if defined(ARCHITECTURE_x86_64) || defined(__aarch64__)
-#define EMU_ARCH_BITS 64
-#elif defined(__i386) || defined(_M_IX86) || defined(__arm__) || defined(_M_ARM)
-#define EMU_ARCH_BITS 32
-#endif
diff --git a/src/common/string_util.cpp b/src/common/string_util.cpp
index e9a2a6b00..646400db0 100644
--- a/src/common/string_util.cpp
+++ b/src/common/string_util.cpp
@@ -46,76 +46,6 @@ bool AsciiToHex(const char* _szValue, u32& result) {
return true;
}
-bool CharArrayFromFormatV(char* out, int outsize, const char* format, va_list args) {
- int writtenCount;
-
-#ifdef _MSC_VER
- // You would think *printf are simple, right? Iterate on each character,
- // if it's a format specifier handle it properly, etc.
- //
- // Nooooo. Not according to the C standard.
- //
- // According to the C99 standard (7.19.6.1 "The fprintf function")
- // The format shall be a multibyte character sequence
- //
- // Because some character encodings might have '%' signs in the middle of
- // a multibyte sequence (SJIS for example only specifies that the first
- // byte of a 2 byte sequence is "high", the second byte can be anything),
- // printf functions have to decode the multibyte sequences and try their
- // best to not screw up.
- //
- // Unfortunately, on Windows, the locale for most languages is not UTF-8
- // as we would need. Notably, for zh_TW, Windows chooses EUC-CN as the
- // locale, and completely fails when trying to decode UTF-8 as EUC-CN.
- //
- // On the other hand, the fix is simple: because we use UTF-8, no such
- // multibyte handling is required as we can simply assume that no '%' char
- // will be present in the middle of a multibyte sequence.
- //
- // This is why we lookup an ANSI (cp1252) locale here and use _vsnprintf_l.
- static locale_t c_locale = nullptr;
- if (!c_locale)
- c_locale = _create_locale(LC_ALL, ".1252");
- writtenCount = _vsnprintf_l(out, outsize, format, c_locale, args);
-#else
- writtenCount = vsnprintf(out, outsize, format, args);
-#endif
-
- if (writtenCount > 0 && writtenCount < outsize) {
- out[writtenCount] = '\0';
- return true;
- } else {
- out[outsize - 1] = '\0';
- return false;
- }
-}
-
-std::string StringFromFormat(const char* format, ...) {
- va_list args;
- char* buf = nullptr;
-#ifdef _WIN32
- int required = 0;
-
- va_start(args, format);
- required = _vscprintf(format, args);
- buf = new char[required + 1];
- CharArrayFromFormatV(buf, required + 1, format, args);
- va_end(args);
-
- std::string temp = buf;
- delete[] buf;
-#else
- va_start(args, format);
- if (vasprintf(&buf, format, args) < 0)
- LOG_ERROR(Common, "Unable to allocate memory for string");
- va_end(args);
-
- std::string temp = buf;
- free(buf);
-#endif
- return temp;
-}
-
// For Debugging. Read out an u8 array.
std::string ArrayToString(const u8* data, size_t size, int line_len, bool spaces) {
std::ostringstream oss;
@@ -134,6 +64,10 @@ std::string ArrayToString(const u8* data, size_t size, int line_len, bool spaces
return oss.str();
}
+std::string StringFromBuffer(const std::vector<u8>& data) {
+ return std::string(data.begin(), std::find(data.begin(), data.end(), '\0'));
+}
+
// Turns " hej " into "hej". Also handles tabs.
std::string StripSpaces(const std::string& str) {
const size_t s = str.find_first_not_of(" \t\r\n");
@@ -347,7 +281,7 @@ static std::string CodeToUTF8(const char* fromcode, const std::basic_string<T>&
iconv_t const conv_desc = iconv_open("UTF-8", fromcode);
if ((iconv_t)(-1) == conv_desc) {
- LOG_ERROR(Common, "Iconv initialization failure [%s]: %s", fromcode, strerror(errno));
+ NGLOG_ERROR(Common, "Iconv initialization failure [{}]: {}", fromcode, strerror(errno));
iconv_close(conv_desc);
return {};
}
@@ -376,7 +310,7 @@ static std::string CodeToUTF8(const char* fromcode, const std::basic_string<T>&
++src_buffer;
}
} else {
- LOG_ERROR(Common, "iconv failure [%s]: %s", fromcode, strerror(errno));
+ NGLOG_ERROR(Common, "iconv failure [{}]: {}", fromcode, strerror(errno));
break;
}
}
@@ -395,7 +329,7 @@ std::u16string UTF8ToUTF16(const std::string& input) {
iconv_t const conv_desc = iconv_open("UTF-16LE", "UTF-8");
if ((iconv_t)(-1) == conv_desc) {
- LOG_ERROR(Common, "Iconv initialization failure [UTF-8]: %s", strerror(errno));
+ NGLOG_ERROR(Common, "Iconv initialization failure [UTF-8]: {}", strerror(errno));
iconv_close(conv_desc);
return {};
}
@@ -424,7 +358,7 @@ std::u16string UTF8ToUTF16(const std::string& input) {
++src_buffer;
}
} else {
- LOG_ERROR(Common, "iconv failure [UTF-8]: %s", strerror(errno));
+ NGLOG_ERROR(Common, "iconv failure [UTF-8]: {}", strerror(errno));
break;
}
}
@@ -462,4 +396,27 @@ std::string StringFromFixedZeroTerminatedBuffer(const char* buffer, size_t max_l
return std::string(buffer, len);
}
+
+const char* TrimSourcePath(const char* path, const char* root) {
+ const char* p = path;
+
+ while (*p != '\0') {
+ const char* next_slash = p;
+ while (*next_slash != '\0' && *next_slash != '/' && *next_slash != '\\') {
+ ++next_slash;
+ }
+
+ bool is_src = Common::ComparePartialString(p, next_slash, root);
+ p = next_slash;
+
+ if (*p != '\0') {
+ ++p;
+ }
+ if (is_src) {
+ path = p;
+ }
+ }
+ return path;
+}
+
} // namespace Common
diff --git a/src/common/string_util.h b/src/common/string_util.h
index ceb8df48e..1f5a383cb 100644
--- a/src/common/string_util.h
+++ b/src/common/string_util.h
@@ -4,7 +4,6 @@
#pragma once
-#include <cstdarg>
#include <cstddef>
#include <iomanip>
#include <sstream>
@@ -20,21 +19,10 @@ std::string ToLower(std::string str);
/// Make a string uppercase
std::string ToUpper(std::string str);
-std::string StringFromFormat(const char* format, ...);
-// Cheap!
-bool CharArrayFromFormatV(char* out, int outsize, const char* format, va_list args);
-
-template <size_t Count>
-inline void CharArrayFromFormat(char (&out)[Count], const char* format, ...) {
- va_list args;
- va_start(args, format);
- CharArrayFromFormatV(out, Count, format, args);
- va_end(args);
-}
-
-// Good
std::string ArrayToString(const u8* data, size_t size, int line_len = 20, bool spaces = true);
+std::string StringFromBuffer(const std::vector<u8>& data);
+
std::string StripSpaces(const std::string& s);
std::string StripQuotes(const std::string& s);
@@ -134,4 +122,17 @@ bool ComparePartialString(InIt begin, InIt end, const char* other) {
* NUL-terminated then the string ends at max_len characters.
*/
std::string StringFromFixedZeroTerminatedBuffer(const char* buffer, size_t max_len);
+
+/**
+ * Attempts to trim an arbitrary prefix from `path`, leaving only the part starting at `root`. It's
+ * intended to be used to strip a system-specific build directory from the `__FILE__` macro,
+ * leaving only the path relative to the sources root.
+ *
+ * @param path The input file path as a null-terminated string
+ * @param root The name of the root source directory as a null-terminated string. Path up to and
+ * including the last occurrence of this name will be stripped
+ * @return A pointer to the same string passed as `path`, but starting at the trimmed portion
+ */
+const char* TrimSourcePath(const char* path, const char* root = "src");
+
} // namespace Common
diff --git a/src/common/swap.h b/src/common/swap.h
index d94cbe6b2..4a4012d1a 100644
--- a/src/common/swap.h
+++ b/src/common/swap.h
@@ -103,7 +103,19 @@ inline __attribute__((always_inline)) u64 swap64(u64 _data) {
return __builtin_bswap64(_data);
}
#elif defined(__Bitrig__) || defined(__OpenBSD__)
-// swap16, swap32, swap64 are left as is
+// 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);
diff --git a/src/common/telemetry.h b/src/common/telemetry.h
index 3694c76f2..7a09df0a7 100644
--- a/src/common/telemetry.h
+++ b/src/common/telemetry.h
@@ -15,7 +15,7 @@ namespace Telemetry {
/// Field type, used for grouping fields together in the final submitted telemetry log
enum class FieldType : u8 {
None = 0, ///< No specified field group
- App, ///< Citra application fields (e.g. version, branch, etc.)
+ App, ///< yuzu application fields (e.g. version, branch, etc.)
Session, ///< Emulated session fields (e.g. title ID, log, etc.)
Performance, ///< Emulated performance (e.g. fps, emulated CPU speed, etc.)
UserFeedback, ///< User submitted feedback (e.g. star rating, user notes, etc.)
diff --git a/src/common/thread.h b/src/common/thread.h
index fa475ab51..9465e1de7 100644
--- a/src/common/thread.h
+++ b/src/common/thread.h
@@ -11,25 +11,6 @@
#include <thread>
#include "common/common_types.h"
-// Support for C++11's thread_local keyword was surprisingly spotty in compilers until very
-// recently. Fortunately, thread local variables have been well supported for compilers for a while,
-// but with semantics supporting only POD types, so we can use a few defines to get some amount of
-// backwards compat support.
-// WARNING: This only works correctly with POD types.
-#if defined(__clang__)
-#if !__has_feature(cxx_thread_local)
-#define thread_local __thread
-#endif
-#elif defined(__GNUC__)
-#if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 8)
-#define thread_local __thread
-#endif
-#elif defined(_MSC_VER)
-#if _MSC_VER < 1900
-#define thread_local __declspec(thread)
-#endif
-#endif
-
namespace Common {
int CurrentThreadId();
diff --git a/src/common/timer.cpp b/src/common/timer.cpp
index c9803109e..f0c5b1a43 100644
--- a/src/common/timer.cpp
+++ b/src/common/timer.cpp
@@ -2,7 +2,10 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include <time.h>
+#include <ctime>
+
+#include <fmt/format.h>
+
#ifdef _WIN32
#include <windows.h>
// windows.h needs to be included before other windows headers
@@ -104,8 +107,8 @@ std::string Timer::GetTimeElapsedFormatted() const {
// Hours
u32 Hours = Minutes / 60;
- std::string TmpStr = StringFromFormat("%02i:%02i:%02i:%03i", Hours, Minutes % 60, Seconds % 60,
- Milliseconds % 1000);
+ std::string TmpStr = fmt::format("{:02}:{:02}:{:02}:{:03}", Hours, Minutes % 60, Seconds % 60,
+ Milliseconds % 1000);
return TmpStr;
}
@@ -165,11 +168,11 @@ std::string Timer::GetTimeFormatted() {
#ifdef _WIN32
struct timeb tp;
(void)::ftime(&tp);
- return StringFromFormat("%s:%03i", tmp, tp.millitm);
+ return fmt::format("{}:{:03}", tmp, tp.millitm);
#else
struct timeval t;
(void)gettimeofday(&t, nullptr);
- return StringFromFormat("%s:%03d", tmp, (int)(t.tv_usec / 1000));
+ return fmt::format("{}:{:03}", tmp, static_cast<int>(t.tv_usec / 1000));
#endif
}
diff --git a/src/common/vector_math.h b/src/common/vector_math.h
index 3f0057d9e..cca43bd4c 100644
--- a/src/common/vector_math.h
+++ b/src/common/vector_math.h
@@ -52,12 +52,8 @@ static inline Vec4<T> MakeVec(const T& x, const T& y, const T& z, const T& w);
template <typename T>
class Vec2 {
public:
- T x;
- T y;
-
- T* AsArray() {
- return &x;
- }
+ T x{};
+ T y{};
Vec2() = default;
Vec2(const T& _x, const T& _y) : x(_x), y(_y) {}
@@ -71,11 +67,6 @@ public:
return Vec2<T>(f, f);
}
- void Write(T a[2]) {
- a[0] = x;
- a[1] = y;
- }
-
Vec2<decltype(T{} + T{})> operator+(const Vec2& other) const {
return MakeVec(x + other.x, y + other.y);
}
@@ -201,13 +192,9 @@ inline float Vec2<float>::Normalize() {
template <typename T>
class Vec3 {
public:
- T x;
- T y;
- T z;
-
- T* AsArray() {
- return &x;
- }
+ T x{};
+ T y{};
+ T z{};
Vec3() = default;
Vec3(const T& _x, const T& _y, const T& _z) : x(_x), y(_y), z(_z) {}
@@ -225,12 +212,6 @@ public:
return MakeVec(f, f, f);
}
- void Write(T a[3]) {
- a[0] = x;
- a[1] = y;
- a[2] = z;
- }
-
Vec3<decltype(T{} + T{})> operator+(const Vec3& other) const {
return MakeVec(x + other.x, y + other.y, z + other.z);
}
@@ -411,14 +392,10 @@ typedef Vec3<float> Vec3f;
template <typename T>
class Vec4 {
public:
- T x;
- T y;
- T z;
- T w;
-
- T* AsArray() {
- return &x;
- }
+ T x{};
+ T y{};
+ T z{};
+ T w{};
Vec4() = default;
Vec4(const T& _x, const T& _y, const T& _z, const T& _w) : x(_x), y(_y), z(_z), w(_w) {}
@@ -436,13 +413,6 @@ public:
return Vec4<T>(f, f, f, f);
}
- void Write(T a[4]) {
- a[0] = x;
- a[1] = y;
- a[2] = z;
- a[3] = w;
- }
-
Vec4<decltype(T{} + T{})> operator+(const Vec4& other) const {
return MakeVec(x + other.x, y + other.y, z + other.z, w + other.w);
}
diff --git a/src/common/x64/cpu_detect.cpp b/src/common/x64/cpu_detect.cpp
index 62f17fbb5..2dfcd39c8 100644
--- a/src/common/x64/cpu_detect.cpp
+++ b/src/common/x64/cpu_detect.cpp
@@ -54,7 +54,7 @@ static CPUCaps Detect() {
caps.num_cores = std::thread::hardware_concurrency();
// Assumes the CPU supports the CPUID instruction. Those that don't would likely not support
- // Citra at all anyway
+ // yuzu at all anyway
int cpu_id[4];
memset(caps.brand_string, 0, sizeof(caps.brand_string));
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 1bc536075..ba5b02174 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -4,12 +4,18 @@ add_library(core STATIC
arm/unicorn/arm_unicorn.h
core.cpp
core.h
+ core_cpu.cpp
+ core_cpu.h
core_timing.cpp
core_timing.h
file_sys/directory.h
+ file_sys/disk_filesystem.cpp
+ file_sys/disk_filesystem.h
file_sys/errors.h
file_sys/filesystem.cpp
file_sys/filesystem.h
+ file_sys/partition_filesystem.cpp
+ file_sys/partition_filesystem.h
file_sys/path_parser.cpp
file_sys/path_parser.h
file_sys/program_metadata.cpp
@@ -18,6 +24,10 @@ add_library(core STATIC
file_sys/romfs_factory.h
file_sys/romfs_filesystem.cpp
file_sys/romfs_filesystem.h
+ file_sys/savedata_factory.cpp
+ file_sys/savedata_factory.h
+ file_sys/sdmc_factory.cpp
+ file_sys/sdmc_factory.h
file_sys/storage.h
frontend/emu_window.cpp
frontend/emu_window.h
@@ -34,8 +44,6 @@ add_library(core STATIC
hle/kernel/client_port.h
hle/kernel/client_session.cpp
hle/kernel/client_session.h
- hle/kernel/condition_variable.cpp
- hle/kernel/condition_variable.h
hle/kernel/errors.h
hle/kernel/event.cpp
hle/kernel/event.h
@@ -82,8 +90,14 @@ add_library(core STATIC
hle/romfs.h
hle/service/acc/acc.cpp
hle/service/acc/acc.h
+ hle/service/acc/acc_aa.cpp
+ hle/service/acc/acc_aa.h
+ hle/service/acc/acc_su.cpp
+ hle/service/acc/acc_su.h
hle/service/acc/acc_u0.cpp
hle/service/acc/acc_u0.h
+ hle/service/acc/acc_u1.cpp
+ hle/service/acc/acc_u1.h
hle/service/am/am.cpp
hle/service/am/am.h
hle/service/am/applet_ae.cpp
@@ -110,6 +124,16 @@ add_library(core STATIC
hle/service/audio/audren_u.h
hle/service/audio/codecctl.cpp
hle/service/audio/codecctl.h
+ hle/service/bcat/module.cpp
+ hle/service/bcat/module.h
+ hle/service/bcat/bcat.cpp
+ hle/service/bcat/bcat.h
+ hle/service/fatal/fatal.cpp
+ hle/service/fatal/fatal.h
+ hle/service/fatal/fatal_p.cpp
+ hle/service/fatal/fatal_p.h
+ hle/service/fatal/fatal_u.cpp
+ hle/service/fatal/fatal_u.h
hle/service/filesystem/filesystem.cpp
hle/service/filesystem/filesystem.h
hle/service/filesystem/fsp_srv.cpp
@@ -118,10 +142,14 @@ add_library(core STATIC
hle/service/friend/friend.h
hle/service/friend/friend_a.cpp
hle/service/friend/friend_a.h
+ hle/service/friend/friend_u.cpp
+ hle/service/friend/friend_u.h
hle/service/hid/hid.cpp
hle/service/hid/hid.h
hle/service/lm/lm.cpp
hle/service/lm/lm.h
+ hle/service/mm/mm_u.cpp
+ hle/service/mm/mm_u.h
hle/service/nifm/nifm.cpp
hle/service/nifm/nifm.h
hle/service/nifm/nifm_a.cpp
@@ -130,6 +158,10 @@ add_library(core STATIC
hle/service/nifm/nifm_s.h
hle/service/nifm/nifm_u.cpp
hle/service/nifm/nifm_u.h
+ hle/service/nfp/nfp.cpp
+ hle/service/nfp/nfp.h
+ hle/service/nfp/nfp_user.cpp
+ hle/service/nfp/nfp_user.h
hle/service/ns/ns.cpp
hle/service/ns/ns.h
hle/service/ns/pl_u.cpp
@@ -145,6 +177,8 @@ add_library(core STATIC
hle/service/nvdrv/devices/nvhost_ctrl_gpu.h
hle/service/nvdrv/devices/nvhost_gpu.cpp
hle/service/nvdrv/devices/nvhost_gpu.h
+ hle/service/nvdrv/devices/nvhost_nvdec.cpp
+ hle/service/nvdrv/devices/nvhost_nvdec.h
hle/service/nvdrv/devices/nvmap.cpp
hle/service/nvdrv/devices/nvmap.h
hle/service/nvdrv/interface.cpp
@@ -157,24 +191,44 @@ add_library(core STATIC
hle/service/nvflinger/buffer_queue.h
hle/service/nvflinger/nvflinger.cpp
hle/service/nvflinger/nvflinger.h
+ hle/service/pctl/module.cpp
+ hle/service/pctl/module.h
hle/service/pctl/pctl.cpp
hle/service/pctl/pctl.h
- hle/service/pctl/pctl_a.cpp
- hle/service/pctl/pctl_a.h
+ hle/service/prepo/prepo.cpp
+ hle/service/prepo/prepo.h
hle/service/service.cpp
hle/service/service.h
hle/service/set/set.cpp
hle/service/set/set.h
+ hle/service/set/set_cal.cpp
+ hle/service/set/set_cal.h
+ hle/service/set/set_fd.cpp
+ hle/service/set/set_fd.h
+ hle/service/set/set_sys.cpp
+ hle/service/set/set_sys.h
+ hle/service/set/settings.cpp
+ hle/service/set/settings.h
hle/service/sm/controller.cpp
hle/service/sm/controller.h
hle/service/sm/sm.cpp
hle/service/sm/sm.h
- hle/service/sockets/bsd_u.cpp
- hle/service/sockets/bsd_u.h
+ hle/service/sockets/bsd.cpp
+ hle/service/sockets/bsd.h
+ hle/service/sockets/nsd.cpp
+ hle/service/sockets/nsd.h
hle/service/sockets/sfdnsres.cpp
hle/service/sockets/sfdnsres.h
hle/service/sockets/sockets.cpp
hle/service/sockets/sockets.h
+ hle/service/spl/csrng.cpp
+ hle/service/spl/csrng.h
+ hle/service/spl/module.cpp
+ hle/service/spl/module.h
+ hle/service/spl/spl.cpp
+ hle/service/spl/spl.h
+ hle/service/ssl/ssl.cpp
+ hle/service/ssl/ssl.h
hle/service/time/time.cpp
hle/service/time/time.h
hle/service/time/time_s.cpp
@@ -209,6 +263,7 @@ add_library(core STATIC
loader/nso.h
memory.cpp
memory.h
+ memory_hook.cpp
memory_hook.h
memory_setup.h
perf_stats.cpp
diff --git a/src/core/arm/arm_interface.h b/src/core/arm/arm_interface.h
index 5ae60214e..32ff3c345 100644
--- a/src/core/arm/arm_interface.h
+++ b/src/core/arm/arm_interface.h
@@ -25,22 +25,18 @@ public:
VAddr tls_address;
};
- /**
- * Runs the CPU for the given number of instructions
- * @param num_instructions Number of instructions to run
- */
- void Run(int num_instructions) {
- ExecuteInstructions(num_instructions);
- this->num_instructions += num_instructions;
- }
+ /// Runs the CPU until an event happens
+ virtual void Run() = 0;
/// Step CPU by one instruction
- void Step() {
- Run(1);
- }
+ virtual void Step() = 0;
+ /// Maps a backing memory region for the CPU
virtual void MapBackingMemory(VAddr address, size_t size, u8* memory,
- Kernel::VMAPermission perms) {}
+ Kernel::VMAPermission perms) = 0;
+
+ /// Unmaps a region of memory that was previously mapped using MapBackingMemory
+ virtual void UnmapMemory(VAddr address, size_t size) = 0;
/// Clear all instruction cache
virtual void ClearInstructionCache() = 0;
@@ -122,19 +118,4 @@ public:
/// Prepare core for thread reschedule (if needed to correctly handle state)
virtual void PrepareReschedule() = 0;
-
- /// Getter for num_instructions
- u64 GetNumInstructions() const {
- return num_instructions;
- }
-
-protected:
- /**
- * Executes the given number of instructions
- * @param num_instructions Number of instructions to executes
- */
- virtual void ExecuteInstructions(int num_instructions) = 0;
-
-private:
- u64 num_instructions = 0; ///< Number of instructions executed
};
diff --git a/src/core/arm/dynarmic/arm_dynarmic.cpp b/src/core/arm/dynarmic/arm_dynarmic.cpp
index e7f6bf8c2..b5db47667 100644
--- a/src/core/arm/dynarmic/arm_dynarmic.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic.cpp
@@ -8,6 +8,7 @@
#include <dynarmic/A64/config.h>
#include "common/logging/log.h"
#include "core/arm/dynarmic/arm_dynarmic.h"
+#include "core/core.h"
#include "core/core_timing.h"
#include "core/hle/kernel/memory.h"
#include "core/hle/kernel/svc.h"
@@ -54,8 +55,8 @@ public:
}
void InterpreterFallback(u64 pc, size_t num_instructions) override {
- LOG_INFO(Core_ARM, "Unicorn fallback @ 0x%" PRIx64 " for %zu instructions (instr = %08x)",
- pc, num_instructions, MemoryReadCode(pc));
+ NGLOG_INFO(Core_ARM, "Unicorn fallback @ 0x{:X} for {} instructions (instr = {:08X})", pc,
+ num_instructions, MemoryReadCode(pc));
ARM_Interface::ThreadContext ctx;
parent.SaveContext(ctx);
@@ -75,7 +76,7 @@ public:
case Dynarmic::A64::Exception::Yield:
return;
default:
- ASSERT_MSG(false, "ExceptionRaised(exception = %zu, pc = %" PRIx64 ")",
+ ASSERT_MSG(false, "ExceptionRaised(exception = {}, pc = {:X})",
static_cast<size_t>(exception), pc);
}
}
@@ -85,28 +86,24 @@ public:
}
void AddTicks(u64 ticks) override {
- if (ticks > ticks_remaining) {
- ticks_remaining = 0;
- return;
- }
- ticks -= ticks_remaining;
+ CoreTiming::AddTicks(ticks - num_interpreted_instructions);
+ num_interpreted_instructions = 0;
}
u64 GetTicksRemaining() override {
- return ticks_remaining;
+ return std::max(CoreTiming::GetDowncount(), 0);
}
u64 GetCNTPCT() override {
return CoreTiming::GetTicks();
}
ARM_Dynarmic& parent;
- size_t ticks_remaining = 0;
size_t num_interpreted_instructions = 0;
u64 tpidrro_el0 = 0;
u64 tpidr_el0 = 0;
};
std::unique_ptr<Dynarmic::A64::Jit> MakeJit(const std::unique_ptr<ARM_Dynarmic_Callbacks>& cb) {
- const auto page_table = Kernel::g_current_process->vm_manager.page_table.pointers.data();
+ const auto page_table = Core::CurrentProcess()->vm_manager.page_table.pointers.data();
Dynarmic::A64::UserConfig config;
config.callbacks = cb.get();
@@ -121,11 +118,22 @@ std::unique_ptr<Dynarmic::A64::Jit> MakeJit(const std::unique_ptr<ARM_Dynarmic_C
return std::make_unique<Dynarmic::A64::Jit>(config);
}
+void ARM_Dynarmic::Run() {
+ ASSERT(Memory::GetCurrentPageTable() == current_page_table);
+
+ jit->Run();
+}
+
+void ARM_Dynarmic::Step() {
+ cb->InterpreterFallback(jit->GetPC(), 1);
+}
+
ARM_Dynarmic::ARM_Dynarmic()
: cb(std::make_unique<ARM_Dynarmic_Callbacks>(*this)), jit(MakeJit(cb)) {
ARM_Interface::ThreadContext ctx;
inner_unicorn.SaveContext(ctx);
LoadContext(ctx);
+ PageTableChanged();
}
ARM_Dynarmic::~ARM_Dynarmic() = default;
@@ -135,6 +143,10 @@ void ARM_Dynarmic::MapBackingMemory(u64 address, size_t size, u8* memory,
inner_unicorn.MapBackingMemory(address, size, memory, perms);
}
+void ARM_Dynarmic::UnmapMemory(u64 address, size_t size) {
+ inner_unicorn.UnmapMemory(address, size);
+}
+
void ARM_Dynarmic::SetPC(u64 pc) {
jit->SetPC(pc);
}
@@ -184,13 +196,6 @@ void ARM_Dynarmic::SetTlsAddress(u64 address) {
cb->tpidrro_el0 = address;
}
-void ARM_Dynarmic::ExecuteInstructions(int num_instructions) {
- cb->ticks_remaining = num_instructions;
- jit->Run();
- CoreTiming::AddTicks(num_instructions - cb->num_interpreted_instructions);
- cb->num_interpreted_instructions = 0;
-}
-
void ARM_Dynarmic::SaveContext(ARM_Interface::ThreadContext& ctx) {
ctx.cpu_registers = jit->GetRegisters();
ctx.sp = jit->GetSP();
@@ -223,4 +228,5 @@ void ARM_Dynarmic::ClearInstructionCache() {
void ARM_Dynarmic::PageTableChanged() {
jit = MakeJit(cb);
+ current_page_table = Memory::GetCurrentPageTable();
}
diff --git a/src/core/arm/dynarmic/arm_dynarmic.h b/src/core/arm/dynarmic/arm_dynarmic.h
index 1d9dcf5ff..128669d01 100644
--- a/src/core/arm/dynarmic/arm_dynarmic.h
+++ b/src/core/arm/dynarmic/arm_dynarmic.h
@@ -19,7 +19,7 @@ public:
void MapBackingMemory(VAddr address, size_t size, u8* memory,
Kernel::VMAPermission perms) override;
-
+ void UnmapMemory(u64 address, size_t size) override;
void SetPC(u64 pc) override;
u64 GetPC() const override;
u64 GetReg(int index) const override;
@@ -29,6 +29,8 @@ public:
u32 GetVFPReg(int index) const override;
void SetVFPReg(int index, u32 value) override;
u32 GetCPSR() const override;
+ void Run() override;
+ void Step() override;
void SetCPSR(u32 cpsr) override;
VAddr GetTlsAddress() const override;
void SetTlsAddress(VAddr address) override;
@@ -37,7 +39,6 @@ public:
void LoadContext(const ThreadContext& ctx) override;
void PrepareReschedule() override;
- void ExecuteInstructions(int num_instructions) override;
void ClearInstructionCache() override;
void PageTableChanged() override;
@@ -47,4 +48,6 @@ private:
std::unique_ptr<ARM_Dynarmic_Callbacks> cb;
std::unique_ptr<Dynarmic::A64::Jit> jit;
ARM_Unicorn inner_unicorn;
+
+ Memory::PageTable* current_page_table = nullptr;
};
diff --git a/src/core/arm/unicorn/arm_unicorn.cpp b/src/core/arm/unicorn/arm_unicorn.cpp
index 5d2956bfd..ce6c5616d 100644
--- a/src/core/arm/unicorn/arm_unicorn.cpp
+++ b/src/core/arm/unicorn/arm_unicorn.cpp
@@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <algorithm>
#include <unicorn/arm64.h>
#include "common/assert.h"
#include "common/microprofile.h"
@@ -29,11 +30,22 @@ LoadDll LoadDll::g_load_dll;
#define CHECKED(expr) \
do { \
if (auto _cerr = (expr)) { \
- ASSERT_MSG(false, "Call " #expr " failed with error: %u (%s)\n", _cerr, \
+ ASSERT_MSG(false, "Call " #expr " failed with error: {} ({})\n", _cerr, \
uc_strerror(_cerr)); \
} \
} while (0)
+static void CodeHook(uc_engine* uc, uint64_t address, uint32_t size, void* user_data) {
+ GDBStub::BreakpointAddress bkpt =
+ GDBStub::GetNextBreakpointFromAddress(address, GDBStub::BreakpointType::Execute);
+ if (GDBStub::IsMemoryBreak() ||
+ (bkpt.type != GDBStub::BreakpointType::None && address == bkpt.address)) {
+ auto core = static_cast<ARM_Unicorn*>(user_data);
+ core->RecordBreak(bkpt);
+ uc_emu_stop(uc);
+ }
+}
+
static void InterruptHook(uc_engine* uc, u32 intNo, void* user_data) {
u32 esr{};
CHECKED(uc_reg_read(uc, UC_ARM64_REG_ESR, &esr));
@@ -51,8 +63,8 @@ static void InterruptHook(uc_engine* uc, u32 intNo, void* user_data) {
static bool UnmappedMemoryHook(uc_engine* uc, uc_mem_type type, u64 addr, int size, u64 value,
void* user_data) {
ARM_Interface::ThreadContext ctx{};
- Core::CPU().SaveContext(ctx);
- ASSERT_MSG(false, "Attempted to read from unmapped memory: 0x%llx, pc=0x%llx, lr=0x%llx", addr,
+ Core::CurrentArmInterface().SaveContext(ctx);
+ ASSERT_MSG(false, "Attempted to read from unmapped memory: 0x{:X}, pc=0x{:X}, lr=0x{:X}", addr,
ctx.pc, ctx.cpu_registers[30]);
return {};
}
@@ -66,6 +78,10 @@ ARM_Unicorn::ARM_Unicorn() {
uc_hook hook{};
CHECKED(uc_hook_add(uc, &hook, UC_HOOK_INTR, (void*)InterruptHook, this, 0, -1));
CHECKED(uc_hook_add(uc, &hook, UC_HOOK_MEM_INVALID, (void*)UnmappedMemoryHook, this, 0, -1));
+ if (GDBStub::IsServerEnabled()) {
+ CHECKED(uc_hook_add(uc, &hook, UC_HOOK_CODE, (void*)CodeHook, this, 0, -1));
+ last_bkpt_hit = false;
+ }
}
ARM_Unicorn::~ARM_Unicorn() {
@@ -77,6 +93,10 @@ void ARM_Unicorn::MapBackingMemory(VAddr address, size_t size, u8* memory,
CHECKED(uc_mem_map_ptr(uc, address, size, static_cast<u32>(perms), memory));
}
+void ARM_Unicorn::UnmapMemory(VAddr address, size_t size) {
+ CHECKED(uc_mem_unmap(uc, address, size));
+}
+
void ARM_Unicorn::SetPC(u64 pc) {
CHECKED(uc_reg_write(uc, UC_ARM64_REG_PC, &pc));
}
@@ -149,12 +169,36 @@ void ARM_Unicorn::SetTlsAddress(VAddr base) {
CHECKED(uc_reg_write(uc, UC_ARM64_REG_TPIDRRO_EL0, &base));
}
+void ARM_Unicorn::Run() {
+ if (GDBStub::IsServerEnabled()) {
+ ExecuteInstructions(std::max(4000000, 0));
+ } else {
+ ExecuteInstructions(std::max(CoreTiming::GetDowncount(), 0));
+ }
+}
+
+void ARM_Unicorn::Step() {
+ ExecuteInstructions(1);
+}
+
MICROPROFILE_DEFINE(ARM_Jit, "ARM JIT", "ARM JIT", MP_RGB(255, 64, 64));
void ARM_Unicorn::ExecuteInstructions(int num_instructions) {
MICROPROFILE_SCOPE(ARM_Jit);
CHECKED(uc_emu_start(uc, GetPC(), 1ULL << 63, 0, num_instructions));
CoreTiming::AddTicks(num_instructions);
+ if (GDBStub::IsServerEnabled()) {
+ if (last_bkpt_hit) {
+ uc_reg_write(uc, UC_ARM64_REG_PC, &last_bkpt.address);
+ }
+ Kernel::Thread* thread = Kernel::GetCurrentThread();
+ SaveContext(thread->context);
+ if (last_bkpt_hit) {
+ last_bkpt_hit = false;
+ GDBStub::Break();
+ }
+ GDBStub::SendTrap(thread, 5);
+ }
}
void ARM_Unicorn::SaveContext(ARM_Interface::ThreadContext& ctx) {
@@ -220,3 +264,8 @@ void ARM_Unicorn::PrepareReschedule() {
}
void ARM_Unicorn::ClearInstructionCache() {}
+
+void ARM_Unicorn::RecordBreak(GDBStub::BreakpointAddress bkpt) {
+ last_bkpt = bkpt;
+ last_bkpt_hit = true;
+}
diff --git a/src/core/arm/unicorn/arm_unicorn.h b/src/core/arm/unicorn/arm_unicorn.h
index c9a561dec..a482a2aa3 100644
--- a/src/core/arm/unicorn/arm_unicorn.h
+++ b/src/core/arm/unicorn/arm_unicorn.h
@@ -7,6 +7,7 @@
#include <unicorn/unicorn.h>
#include "common/common_types.h"
#include "core/arm/arm_interface.h"
+#include "core/gdbstub/gdbstub.h"
class ARM_Unicorn final : public ARM_Interface {
public:
@@ -14,6 +15,7 @@ public:
~ARM_Unicorn();
void MapBackingMemory(VAddr address, size_t size, u8* memory,
Kernel::VMAPermission perms) override;
+ void UnmapMemory(VAddr address, size_t size) override;
void SetPC(u64 pc) override;
u64 GetPC() const override;
u64 GetReg(int index) const override;
@@ -29,10 +31,15 @@ public:
void SaveContext(ThreadContext& ctx) override;
void LoadContext(const ThreadContext& ctx) override;
void PrepareReschedule() override;
- void ExecuteInstructions(int num_instructions) override;
+ void ExecuteInstructions(int num_instructions);
+ void Run() override;
+ void Step() override;
void ClearInstructionCache() override;
void PageTableChanged() override{};
+ void RecordBreak(GDBStub::BreakpointAddress bkpt);
private:
uc_engine* uc{};
+ GDBStub::BreakpointAddress last_bkpt{};
+ bool last_bkpt_hit;
};
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 4fb035556..84ab876cc 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -5,17 +5,16 @@
#include <memory>
#include <utility>
#include "common/logging/log.h"
-#ifdef ARCHITECTURE_x86_64
-#include "core/arm/dynarmic/arm_dynarmic.h"
-#endif
-#include "core/arm/unicorn/arm_unicorn.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/gdbstub/gdbstub.h"
+#include "core/hle/kernel/client_port.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/service/service.h"
+#include "core/hle/service/sm/controller.h"
+#include "core/hle/service/sm/sm.h"
#include "core/hw/hw.h"
#include "core/loader/loader.h"
#include "core/memory_setup.h"
@@ -26,12 +25,34 @@ namespace Core {
/*static*/ System System::s_instance;
-System::ResultStatus System::RunLoop(int tight_loop) {
- status = ResultStatus::Success;
- if (!cpu_core) {
- return ResultStatus::ErrorNotInitialized;
+System::~System() = default;
+
+/// Runs a CPU core while the system is powered on
+static void RunCpuCore(std::shared_ptr<Cpu> cpu_state) {
+ while (Core::System().GetInstance().IsPoweredOn()) {
+ cpu_state->RunLoop(true);
+ }
+}
+
+Cpu& System::CurrentCpuCore() {
+ // If multicore is enabled, use host thread to figure out the current CPU core
+ if (Settings::values.use_multi_core) {
+ const auto& search = thread_to_cpu.find(std::this_thread::get_id());
+ ASSERT(search != thread_to_cpu.end());
+ ASSERT(search->second);
+ return *search->second;
}
+ // Otherwise, use single-threaded mode active_core variable
+ return *cpu_cores[active_core];
+}
+
+System::ResultStatus System::RunLoop(bool tight_loop) {
+ status = ResultStatus::Success;
+
+ // Update thread_to_cpu in case Core 0 is run from a different host thread
+ thread_to_cpu[std::this_thread::get_id()] = cpu_cores[0];
+
if (GDBStub::IsServerEnabled()) {
GDBStub::HandlePacket();
@@ -40,54 +61,49 @@ System::ResultStatus System::RunLoop(int tight_loop) {
if (GDBStub::GetCpuHaltFlag()) {
if (GDBStub::GetCpuStepFlag()) {
GDBStub::SetCpuStepFlag(false);
- tight_loop = 1;
+ tight_loop = false;
} else {
return ResultStatus::Success;
}
}
}
- // If we don't have a currently active thread then don't execute instructions,
- // instead advance to the next event and try to yield to the next thread
- if (Kernel::GetCurrentThread() == nullptr) {
- LOG_TRACE(Core_ARM, "Idling");
- CoreTiming::Idle();
- CoreTiming::Advance();
- PrepareReschedule();
- } else {
- CoreTiming::Advance();
- cpu_core->Run(tight_loop);
+ for (active_core = 0; active_core < NUM_CPU_CORES; ++active_core) {
+ cpu_cores[active_core]->RunLoop(tight_loop);
+ if (Settings::values.use_multi_core) {
+ // Cores 1-3 are run on other threads in this mode
+ break;
+ }
}
- HW::Update();
- Reschedule();
-
return status;
}
System::ResultStatus System::SingleStep() {
- return RunLoop(1);
+ return RunLoop(false);
}
System::ResultStatus System::Load(EmuWindow* emu_window, const std::string& filepath) {
app_loader = Loader::GetLoader(filepath);
if (!app_loader) {
- LOG_CRITICAL(Core, "Failed to obtain loader for %s!", filepath.c_str());
+ NGLOG_CRITICAL(Core, "Failed to obtain loader for {}!", filepath);
return ResultStatus::ErrorGetLoader;
}
std::pair<boost::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 %i)!",
- static_cast<int>(system_mode.second));
+ NGLOG_CRITICAL(Core, "Failed to determine system mode (Error {})!",
+ static_cast<int>(system_mode.second));
switch (system_mode.second) {
case Loader::ResultStatus::ErrorEncrypted:
return ResultStatus::ErrorLoader_ErrorEncrypted;
case Loader::ResultStatus::ErrorInvalidFormat:
return ResultStatus::ErrorLoader_ErrorInvalidFormat;
+ case Loader::ResultStatus::ErrorUnsupportedArch:
+ return ResultStatus::ErrorUnsupportedArch;
default:
return ResultStatus::ErrorSystemMode;
}
@@ -95,14 +111,15 @@ System::ResultStatus System::Load(EmuWindow* emu_window, const std::string& file
ResultStatus init_result{Init(emu_window, system_mode.first.get())};
if (init_result != ResultStatus::Success) {
- LOG_CRITICAL(Core, "Failed to initialize system (Error %i)!", init_result);
+ NGLOG_CRITICAL(Core, "Failed to initialize system (Error {})!",
+ static_cast<int>(init_result));
System::Shutdown();
return init_result;
}
- const Loader::ResultStatus load_result{app_loader->Load(Kernel::g_current_process)};
+ const Loader::ResultStatus load_result{app_loader->Load(current_process)};
if (Loader::ResultStatus::Success != load_result) {
- LOG_CRITICAL(Core, "Failed to load ROM (Error %i)!", load_result);
+ NGLOG_CRITICAL(Core, "Failed to load ROM (Error {})!", static_cast<int>(load_result));
System::Shutdown();
switch (load_result) {
@@ -110,6 +127,8 @@ System::ResultStatus System::Load(EmuWindow* emu_window, const std::string& file
return ResultStatus::ErrorLoader_ErrorEncrypted;
case Loader::ResultStatus::ErrorInvalidFormat:
return ResultStatus::ErrorLoader_ErrorInvalidFormat;
+ case Loader::ResultStatus::ErrorUnsupportedArch:
+ return ResultStatus::ErrorUnsupportedArch;
default:
return ResultStatus::ErrorLoader;
}
@@ -119,58 +138,65 @@ System::ResultStatus System::Load(EmuWindow* emu_window, const std::string& file
}
void System::PrepareReschedule() {
- cpu_core->PrepareReschedule();
- reschedule_pending = true;
+ CurrentCpuCore().PrepareReschedule();
}
PerfStats::Results System::GetAndResetPerfStats() {
return perf_stats.GetAndResetStats(CoreTiming::GetGlobalTimeUs());
}
-void System::Reschedule() {
- if (!reschedule_pending) {
- return;
- }
+const std::shared_ptr<Kernel::Scheduler>& System::Scheduler(size_t core_index) {
+ ASSERT(core_index < NUM_CPU_CORES);
+ return cpu_cores[core_index]->Scheduler();
+}
- reschedule_pending = false;
- Core::System::GetInstance().Scheduler().Reschedule();
+ARM_Interface& System::ArmInterface(size_t core_index) {
+ ASSERT(core_index < NUM_CPU_CORES);
+ return cpu_cores[core_index]->ArmInterface();
+}
+
+Cpu& System::CpuCore(size_t core_index) {
+ ASSERT(core_index < NUM_CPU_CORES);
+ return *cpu_cores[core_index];
}
System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) {
- LOG_DEBUG(HW_Memory, "initialized OK");
+ NGLOG_DEBUG(HW_Memory, "initialized OK");
CoreTiming::Init();
- switch (Settings::values.cpu_core) {
- case Settings::CpuCore::Unicorn:
- cpu_core = std::make_shared<ARM_Unicorn>();
- break;
- case Settings::CpuCore::Dynarmic:
- default:
-#ifdef ARCHITECTURE_x86_64
- cpu_core = std::make_shared<ARM_Dynarmic>();
-#else
- cpu_core = std::make_shared<ARM_Unicorn>();
- LOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available");
-#endif
- break;
+ current_process = Kernel::Process::Create("main");
+
+ cpu_barrier = std::make_shared<CpuBarrier>();
+ for (size_t index = 0; index < cpu_cores.size(); ++index) {
+ cpu_cores[index] = std::make_shared<Cpu>(cpu_barrier, index);
}
gpu_core = std::make_unique<Tegra::GPU>();
-
telemetry_session = std::make_unique<Core::TelemetrySession>();
+ service_manager = std::make_shared<Service::SM::ServiceManager>();
HW::Init();
Kernel::Init(system_mode);
- scheduler = std::make_unique<Kernel::Scheduler>(cpu_core.get());
- Service::Init();
+ Service::Init(service_manager);
GDBStub::Init();
if (!VideoCore::Init(emu_window)) {
return ResultStatus::ErrorVideoCore;
}
- LOG_DEBUG(Core, "Initialized OK");
+ // 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()] = cpu_cores[0];
+ if (Settings::values.use_multi_core) {
+ for (size_t index = 0; index < cpu_core_threads.size(); ++index) {
+ cpu_core_threads[index] =
+ std::make_unique<std::thread>(RunCpuCore, cpu_cores[index + 1]);
+ thread_to_cpu[cpu_core_threads[index]->get_id()] = cpu_cores[index + 1];
+ }
+ }
+
+ NGLOG_DEBUG(Core, "Initialized OK");
// Reset counters and set time origin to current frame
GetAndResetPerfStats();
@@ -193,17 +219,41 @@ void System::Shutdown() {
VideoCore::Shutdown();
GDBStub::Shutdown();
Service::Shutdown();
- scheduler = nullptr;
Kernel::Shutdown();
HW::Shutdown();
- telemetry_session = nullptr;
- gpu_core = nullptr;
- cpu_core = nullptr;
+ service_manager.reset();
+ telemetry_session.reset();
+ gpu_core.reset();
+
+ // Close all CPU/threading state
+ cpu_barrier->NotifyEnd();
+ if (Settings::values.use_multi_core) {
+ for (auto& thread : cpu_core_threads) {
+ thread->join();
+ thread.reset();
+ }
+ }
+ thread_to_cpu.clear();
+ for (auto& cpu_core : cpu_cores) {
+ cpu_core.reset();
+ }
+ cpu_barrier.reset();
+
+ // Close core timing
CoreTiming::Shutdown();
- app_loader = nullptr;
+ // Close app loader
+ app_loader.reset();
+
+ NGLOG_DEBUG(Core, "Shutdown OK");
+}
+
+Service::SM::ServiceManager& System::ServiceManager() {
+ return *service_manager;
+}
- LOG_DEBUG(Core, "Shutdown OK");
+const Service::SM::ServiceManager& System::ServiceManager() const {
+ return *service_manager;
}
} // namespace Core
diff --git a/src/core/core.h b/src/core/core.h
index ada23b347..f90f085ad 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -4,23 +4,34 @@
#pragma once
+#include <array>
#include <memory>
#include <string>
+#include <thread>
#include "common/common_types.h"
+#include "core/core_cpu.h"
+#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/scheduler.h"
#include "core/loader/loader.h"
#include "core/memory.h"
#include "core/perf_stats.h"
#include "core/telemetry_session.h"
+#include "video_core/debug_utils/debug_utils.h"
#include "video_core/gpu.h"
class EmuWindow;
class ARM_Interface;
+namespace Service::SM {
+class ServiceManager;
+}
+
namespace Core {
class System {
public:
+ ~System();
+
/**
* Gets the instance of the System singleton class.
* @returns Reference to the instance of the System singleton class.
@@ -42,6 +53,7 @@ public:
ErrorSystemFiles, ///< Error in finding system files
ErrorSharedFont, ///< Error in finding shared font
ErrorVideoCore, ///< Error in the video core
+ ErrorUnsupportedArch, ///< Unsupported Architecture (32-Bit ROMs)
ErrorUnknown ///< Any other error
};
@@ -52,10 +64,10 @@ public:
* is not required to do a full dispatch with each instruction. NOTE: the number of instructions
* requested is not guaranteed to run, as this will be interrupted preemptively if a hardware
* update is requested (e.g. on a thread switch).
- * @param tight_loop Number of instructions to execute.
+ * @param tight_loop If false, the CPU single-steps.
* @return Result status, indicating whether or not the operation succeeded.
*/
- ResultStatus RunLoop(int tight_loop = 100000);
+ ResultStatus RunLoop(bool tight_loop = true);
/**
* Step the CPU one instruction
@@ -80,7 +92,7 @@ public:
* @returns True if the emulated system is powered on, otherwise false.
*/
bool IsPoweredOn() const {
- return cpu_core != nullptr;
+ return cpu_barrier && cpu_barrier->IsAlive();
}
/**
@@ -94,22 +106,36 @@ public:
/// Prepare the core emulation for a reschedule
void PrepareReschedule();
+ /// Gets and resets core performance statistics
PerfStats::Results GetAndResetPerfStats();
- /**
- * Gets a reference to the emulated CPU.
- * @returns A reference to the emulated CPU.
- */
- ARM_Interface& CPU() {
- return *cpu_core;
+ /// Gets an ARM interface to the CPU core that is currently running
+ ARM_Interface& CurrentArmInterface() {
+ return CurrentCpuCore().ArmInterface();
}
+ /// Gets an ARM interface to the CPU core with the specified index
+ ARM_Interface& ArmInterface(size_t core_index);
+
+ /// Gets a CPU interface to the CPU core with the specified index
+ Cpu& CpuCore(size_t core_index);
+
+ /// Gets the GPU interface
Tegra::GPU& GPU() {
return *gpu_core;
}
- Kernel::Scheduler& Scheduler() {
- return *scheduler;
+ /// Gets the scheduler for the CPU core that is currently running
+ Kernel::Scheduler& CurrentScheduler() {
+ return *CurrentCpuCore().Scheduler();
+ }
+
+ /// Gets the scheduler for the CPU core with the specified index
+ const std::shared_ptr<Kernel::Scheduler>& Scheduler(size_t core_index);
+
+ /// Gets the current process
+ Kernel::SharedPtr<Kernel::Process>& CurrentProcess() {
+ return current_process;
}
PerfStats perf_stats;
@@ -130,7 +156,21 @@ public:
return *app_loader;
}
+ Service::SM::ServiceManager& ServiceManager();
+ const Service::SM::ServiceManager& ServiceManager() const;
+
+ void SetGPUDebugContext(std::shared_ptr<Tegra::DebugContext> context) {
+ debug_context = std::move(context);
+ }
+
+ std::shared_ptr<Tegra::DebugContext> GetGPUDebugContext() const {
+ return debug_context;
+ }
+
private:
+ /// Returns the currently running CPU core
+ Cpu& CurrentCpuCore();
+
/**
* Initialize the emulated system.
* @param emu_window Pointer to the host-system window used for video output and keyboard input.
@@ -139,18 +179,18 @@ private:
*/
ResultStatus Init(EmuWindow* emu_window, u32 system_mode);
- /// Reschedule the core emulation
- void Reschedule();
-
/// AppLoader used to load the current executing application
std::unique_ptr<Loader::AppLoader> app_loader;
-
- std::shared_ptr<ARM_Interface> cpu_core;
- std::unique_ptr<Kernel::Scheduler> scheduler;
std::unique_ptr<Tegra::GPU> gpu_core;
+ std::shared_ptr<Tegra::DebugContext> debug_context;
+ Kernel::SharedPtr<Kernel::Process> current_process;
+ std::shared_ptr<CpuBarrier> cpu_barrier;
+ std::array<std::shared_ptr<Cpu>, NUM_CPU_CORES> cpu_cores;
+ std::array<std::unique_ptr<std::thread>, NUM_CPU_CORES - 1> cpu_core_threads;
+ size_t active_core{}; ///< Active core, only used in single thread mode
- /// When true, signals that a reschedule should happen
- bool reschedule_pending{};
+ /// Service manager
+ std::shared_ptr<Service::SM::ServiceManager> service_manager;
/// Telemetry session for this emulation session
std::unique_ptr<Core::TelemetrySession> telemetry_session;
@@ -159,14 +199,21 @@ private:
ResultStatus status = ResultStatus::Success;
std::string status_details = "";
+
+ /// Map of guest threads to CPU cores
+ std::map<std::thread::id, std::shared_ptr<Cpu>> thread_to_cpu;
};
-inline ARM_Interface& CPU() {
- return System::GetInstance().CPU();
+inline ARM_Interface& CurrentArmInterface() {
+ return System::GetInstance().CurrentArmInterface();
}
inline TelemetrySession& Telemetry() {
return System::GetInstance().TelemetrySession();
}
+inline Kernel::SharedPtr<Kernel::Process>& CurrentProcess() {
+ return System::GetInstance().CurrentProcess();
+}
+
} // namespace Core
diff --git a/src/core/core_cpu.cpp b/src/core/core_cpu.cpp
new file mode 100644
index 000000000..099f2bb1a
--- /dev/null
+++ b/src/core/core_cpu.cpp
@@ -0,0 +1,119 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <condition_variable>
+#include <mutex>
+
+#include "common/logging/log.h"
+#ifdef ARCHITECTURE_x86_64
+#include "core/arm/dynarmic/arm_dynarmic.h"
+#endif
+#include "core/arm/unicorn/arm_unicorn.h"
+#include "core/core_cpu.h"
+#include "core/core_timing.h"
+#include "core/hle/kernel/kernel.h"
+#include "core/hle/kernel/scheduler.h"
+#include "core/hle/kernel/thread.h"
+#include "core/settings.h"
+
+namespace Core {
+
+void CpuBarrier::NotifyEnd() {
+ std::unique_lock<std::mutex> lock(mutex);
+ end = true;
+ condition.notify_all();
+}
+
+bool CpuBarrier::Rendezvous() {
+ if (!Settings::values.use_multi_core) {
+ // Meaningless when running in single-core mode
+ return true;
+ }
+
+ if (!end) {
+ std::unique_lock<std::mutex> lock(mutex);
+
+ --cores_waiting;
+ if (!cores_waiting) {
+ cores_waiting = NUM_CPU_CORES;
+ condition.notify_all();
+ return true;
+ }
+
+ condition.wait(lock);
+ return true;
+ }
+
+ return false;
+}
+
+Cpu::Cpu(std::shared_ptr<CpuBarrier> cpu_barrier, size_t core_index)
+ : cpu_barrier{std::move(cpu_barrier)}, core_index{core_index} {
+
+ if (Settings::values.use_cpu_jit) {
+#ifdef ARCHITECTURE_x86_64
+ arm_interface = std::make_shared<ARM_Dynarmic>();
+#else
+ cpu_core = std::make_shared<ARM_Unicorn>();
+ NGLOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available");
+#endif
+ } else {
+ arm_interface = std::make_shared<ARM_Unicorn>();
+ }
+
+ scheduler = std::make_shared<Kernel::Scheduler>(arm_interface.get());
+}
+
+void Cpu::RunLoop(bool tight_loop) {
+ // Wait for all other CPU cores to complete the previous slice, such that they run in lock-step
+ if (!cpu_barrier->Rendezvous()) {
+ // If rendezvous failed, session has been killed
+ return;
+ }
+
+ // If we don't have a currently active thread then don't execute instructions,
+ // instead advance to the next event and try to yield to the next thread
+ if (Kernel::GetCurrentThread() == nullptr) {
+ NGLOG_TRACE(Core, "Core-{} idling", core_index);
+
+ if (IsMainCore()) {
+ CoreTiming::Idle();
+ CoreTiming::Advance();
+ }
+
+ PrepareReschedule();
+ } else {
+ if (IsMainCore()) {
+ CoreTiming::Advance();
+ }
+
+ if (tight_loop) {
+ arm_interface->Run();
+ } else {
+ arm_interface->Step();
+ }
+ }
+
+ Reschedule();
+}
+
+void Cpu::SingleStep() {
+ return RunLoop(false);
+}
+
+void Cpu::PrepareReschedule() {
+ arm_interface->PrepareReschedule();
+ reschedule_pending = true;
+}
+
+void Cpu::Reschedule() {
+ if (!reschedule_pending) {
+ return;
+ }
+
+ reschedule_pending = false;
+ scheduler->Reschedule();
+}
+
+} // namespace Core
diff --git a/src/core/core_cpu.h b/src/core/core_cpu.h
new file mode 100644
index 000000000..243f0b5e7
--- /dev/null
+++ b/src/core/core_cpu.h
@@ -0,0 +1,78 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <atomic>
+#include <condition_variable>
+#include <memory>
+#include <mutex>
+#include <string>
+#include "common/common_types.h"
+
+class ARM_Interface;
+
+namespace Kernel {
+class Scheduler;
+}
+
+namespace Core {
+
+constexpr unsigned NUM_CPU_CORES{4};
+
+class CpuBarrier {
+public:
+ bool IsAlive() const {
+ return !end;
+ }
+
+ void NotifyEnd();
+
+ bool Rendezvous();
+
+private:
+ unsigned cores_waiting{NUM_CPU_CORES};
+ std::mutex mutex;
+ std::condition_variable condition;
+ std::atomic<bool> end{};
+};
+
+class Cpu {
+public:
+ Cpu(std::shared_ptr<CpuBarrier> cpu_barrier, size_t core_index);
+
+ void RunLoop(bool tight_loop = true);
+
+ void SingleStep();
+
+ void PrepareReschedule();
+
+ ARM_Interface& ArmInterface() {
+ return *arm_interface;
+ }
+
+ const ARM_Interface& ArmInterface() const {
+ return *arm_interface;
+ }
+
+ const std::shared_ptr<Kernel::Scheduler>& Scheduler() const {
+ return scheduler;
+ }
+
+ bool IsMainCore() const {
+ return core_index == 0;
+ }
+
+private:
+ void Reschedule();
+
+ std::shared_ptr<ARM_Interface> arm_interface;
+ std::shared_ptr<CpuBarrier> cpu_barrier;
+ std::shared_ptr<Kernel::Scheduler> scheduler;
+
+ bool reschedule_pending{};
+ size_t core_index;
+};
+
+} // namespace Core
diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp
index 9e1bf2d0e..dc1d8668f 100644
--- a/src/core/core_timing.cpp
+++ b/src/core/core_timing.cpp
@@ -6,6 +6,7 @@
#include <algorithm>
#include <cinttypes>
+#include <limits>
#include <mutex>
#include <string>
#include <tuple>
@@ -57,7 +58,8 @@ static u64 event_fifo_id;
// to the event_queue by the emu thread
static Common::MPSCQueue<Event, false> ts_queue;
-static constexpr int MAX_SLICE_LENGTH = 20000;
+constexpr int MAX_SLICE_LENGTH = 20000;
+constexpr u64 MAX_VALUE_TO_MULTIPLY = std::numeric_limits<s64>::max() / BASE_CLOCK_RATE;
static s64 idled_cycles;
@@ -70,11 +72,59 @@ static EventType* ev_lost = nullptr;
static void EmptyTimedCallback(u64 userdata, s64 cyclesLate) {}
+s64 usToCycles(s64 us) {
+ if (us / 1000000 > MAX_VALUE_TO_MULTIPLY) {
+ NGLOG_ERROR(Core_Timing, "Integer overflow, use max value");
+ return std::numeric_limits<s64>::max();
+ }
+ if (us > MAX_VALUE_TO_MULTIPLY) {
+ NGLOG_DEBUG(Core_Timing, "Time very big, do rounding");
+ return BASE_CLOCK_RATE * (us / 1000000);
+ }
+ return (BASE_CLOCK_RATE * us) / 1000000;
+}
+
+s64 usToCycles(u64 us) {
+ if (us / 1000000 > MAX_VALUE_TO_MULTIPLY) {
+ NGLOG_ERROR(Core_Timing, "Integer overflow, use max value");
+ return std::numeric_limits<s64>::max();
+ }
+ if (us > MAX_VALUE_TO_MULTIPLY) {
+ NGLOG_DEBUG(Core_Timing, "Time very big, do rounding");
+ return BASE_CLOCK_RATE * static_cast<s64>(us / 1000000);
+ }
+ return (BASE_CLOCK_RATE * static_cast<s64>(us)) / 1000000;
+}
+
+s64 nsToCycles(s64 ns) {
+ if (ns / 1000000000 > MAX_VALUE_TO_MULTIPLY) {
+ NGLOG_ERROR(Core_Timing, "Integer overflow, use max value");
+ return std::numeric_limits<s64>::max();
+ }
+ if (ns > MAX_VALUE_TO_MULTIPLY) {
+ NGLOG_DEBUG(Core_Timing, "Time very big, do rounding");
+ return BASE_CLOCK_RATE * (ns / 1000000000);
+ }
+ return (BASE_CLOCK_RATE * ns) / 1000000000;
+}
+
+s64 nsToCycles(u64 ns) {
+ if (ns / 1000000000 > MAX_VALUE_TO_MULTIPLY) {
+ NGLOG_ERROR(Core_Timing, "Integer overflow, use max value");
+ return std::numeric_limits<s64>::max();
+ }
+ if (ns > MAX_VALUE_TO_MULTIPLY) {
+ NGLOG_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;
+}
+
EventType* RegisterEvent(const std::string& name, TimedCallback callback) {
// check for existing type with same name.
// we want event type names to remain unique so that we can use them for serialization.
ASSERT_MSG(event_types.find(name) == event_types.end(),
- "CoreTiming Event \"%s\" is already registered. Events should only be registered "
+ "CoreTiming Event \"{}\" is already registered. Events should only be registered "
"during Init to avoid breaking save states.",
name.c_str());
diff --git a/src/core/core_timing.h b/src/core/core_timing.h
index b9eb38ea4..dc31124a8 100644
--- a/src/core/core_timing.h
+++ b/src/core/core_timing.h
@@ -18,15 +18,14 @@
*/
#include <functional>
-#include <limits>
#include <string>
#include "common/common_types.h"
-#include "common/logging/log.h"
+
+namespace CoreTiming {
// 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 MAX_VALUE_TO_MULTIPLY = std::numeric_limits<s64>::max() / BASE_CLOCK_RATE;
inline s64 msToCycles(int ms) {
// since ms is int there is no way to overflow
@@ -49,29 +48,9 @@ inline s64 usToCycles(int us) {
return (BASE_CLOCK_RATE * static_cast<s64>(us) / 1000000);
}
-inline s64 usToCycles(s64 us) {
- if (us / 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) {
- LOG_DEBUG(Core_Timing, "Time very big, do rounding");
- return BASE_CLOCK_RATE * (us / 1000000);
- }
- return (BASE_CLOCK_RATE * us) / 1000000;
-}
+s64 usToCycles(s64 us);
-inline s64 usToCycles(u64 us) {
- if (us / 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) {
- LOG_DEBUG(Core_Timing, "Time very big, do rounding");
- return BASE_CLOCK_RATE * static_cast<s64>(us / 1000000);
- }
- return (BASE_CLOCK_RATE * static_cast<s64>(us)) / 1000000;
-}
+s64 usToCycles(u64 us);
inline s64 nsToCycles(float ns) {
return static_cast<s64>(BASE_CLOCK_RATE * (0.000000001f) * ns);
@@ -81,29 +60,9 @@ inline s64 nsToCycles(int ns) {
return BASE_CLOCK_RATE * static_cast<s64>(ns) / 1000000000;
}
-inline s64 nsToCycles(s64 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 * (ns / 1000000000);
- }
- return (BASE_CLOCK_RATE * ns) / 1000000000;
-}
+s64 nsToCycles(s64 ns);
-inline 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;
-}
+s64 nsToCycles(u64 ns);
inline u64 cyclesToNs(s64 cycles) {
return cycles * 1000000000 / BASE_CLOCK_RATE;
@@ -117,8 +76,6 @@ inline u64 cyclesToMs(s64 cycles) {
return cycles * 1000 / BASE_CLOCK_RATE;
}
-namespace CoreTiming {
-
/**
* CoreTiming begins at the boundary of timing slice -1. An initial call to Advance() is
* required to end slice -1 and start slice 0 before the first cycle of code is executed.
diff --git a/src/core/file_sys/directory.h b/src/core/file_sys/directory.h
index 5a40bf472..c7639795e 100644
--- a/src/core/file_sys/directory.h
+++ b/src/core/file_sys/directory.h
@@ -6,34 +6,28 @@
#include <array>
#include <cstddef>
+#include "common/common_funcs.h"
#include "common/common_types.h"
+#include "core/file_sys/filesystem.h"
////////////////////////////////////////////////////////////////////////////////////////////////////
// FileSys namespace
namespace FileSys {
-// Structure of a directory entry, from http://3dbrew.org/wiki/FSDir:Read#Entry_format
-const size_t FILENAME_LENGTH = 0x20C / 2;
+// Structure of a directory entry, from
+// http://switchbrew.org/index.php?title=Filesystem_services#DirectoryEntry
+const size_t FILENAME_LENGTH = 0x300;
struct Entry {
- char16_t filename[FILENAME_LENGTH]; // Entry name (UTF-16, null-terminated)
- std::array<char, 9> short_name; // 8.3 file name ('longfilename' -> 'LONGFI~1', null-terminated)
- char unknown1; // unknown (observed values: 0x0A, 0x70, 0xFD)
- std::array<char, 4>
- extension; // 8.3 file extension (set to spaces for directories, null-terminated)
- char unknown2; // unknown (always 0x01)
- char unknown3; // unknown (0x00 or 0x08)
- char is_directory; // directory flag
- char is_hidden; // hidden flag
- char is_archive; // archive flag
- char is_read_only; // read-only flag
- u64 file_size; // file size (for files only)
+ char filename[FILENAME_LENGTH];
+ INSERT_PADDING_BYTES(4);
+ EntryType type;
+ INSERT_PADDING_BYTES(3);
+ u64 file_size;
};
-static_assert(sizeof(Entry) == 0x228, "Directory Entry struct isn't exactly 0x228 bytes long!");
-static_assert(offsetof(Entry, short_name) == 0x20C, "Wrong offset for short_name in Entry.");
-static_assert(offsetof(Entry, extension) == 0x216, "Wrong offset for extension in Entry.");
-static_assert(offsetof(Entry, is_archive) == 0x21E, "Wrong offset for is_archive in Entry.");
-static_assert(offsetof(Entry, file_size) == 0x220, "Wrong offset for file_size in Entry.");
+static_assert(sizeof(Entry) == 0x310, "Directory Entry struct isn't exactly 0x310 bytes long!");
+static_assert(offsetof(Entry, type) == 0x304, "Wrong offset for type in Entry.");
+static_assert(offsetof(Entry, file_size) == 0x308, "Wrong offset for file_size in Entry.");
class DirectoryBackend : NonCopyable {
public:
@@ -46,7 +40,10 @@ public:
* @param entries Buffer to read data into
* @return Number of entries listed
*/
- virtual u32 Read(const u32 count, Entry* entries) = 0;
+ virtual u64 Read(const u64 count, Entry* entries) = 0;
+
+ /// Returns the number of entries still left to read.
+ virtual u64 GetEntryCount() const = 0;
/**
* Close the directory
diff --git a/src/core/file_sys/disk_filesystem.cpp b/src/core/file_sys/disk_filesystem.cpp
new file mode 100644
index 000000000..8aa0e0aa4
--- /dev/null
+++ b/src/core/file_sys/disk_filesystem.cpp
@@ -0,0 +1,237 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cstring>
+#include <memory>
+#include "common/common_types.h"
+#include "common/logging/log.h"
+#include "core/file_sys/disk_filesystem.h"
+#include "core/file_sys/errors.h"
+
+namespace FileSys {
+
+static std::string ModeFlagsToString(Mode mode) {
+ std::string mode_str;
+ u32 mode_flags = static_cast<u32>(mode);
+
+ // Calculate the correct open mode for the file.
+ if ((mode_flags & static_cast<u32>(Mode::Read)) &&
+ (mode_flags & static_cast<u32>(Mode::Write))) {
+ if (mode_flags & static_cast<u32>(Mode::Append))
+ mode_str = "a+";
+ else
+ mode_str = "r+";
+ } else {
+ if (mode_flags & static_cast<u32>(Mode::Read))
+ mode_str = "r";
+ else if (mode_flags & static_cast<u32>(Mode::Append))
+ mode_str = "a";
+ else if (mode_flags & static_cast<u32>(Mode::Write))
+ mode_str = "w";
+ }
+
+ mode_str += "b";
+
+ return mode_str;
+}
+
+std::string Disk_FileSystem::GetName() const {
+ return "Disk";
+}
+
+ResultVal<std::unique_ptr<StorageBackend>> Disk_FileSystem::OpenFile(const std::string& path,
+ Mode mode) const {
+
+ // Calculate the correct open mode for the file.
+ std::string mode_str = ModeFlagsToString(mode);
+
+ std::string full_path = base_directory + path;
+ auto file = std::make_shared<FileUtil::IOFile>(full_path, mode_str.c_str());
+
+ if (!file->IsOpen()) {
+ return ERROR_PATH_NOT_FOUND;
+ }
+
+ return MakeResult<std::unique_ptr<StorageBackend>>(
+ std::make_unique<Disk_Storage>(std::move(file)));
+}
+
+ResultCode Disk_FileSystem::DeleteFile(const std::string& path) const {
+ if (!FileUtil::Exists(path)) {
+ return ERROR_PATH_NOT_FOUND;
+ }
+
+ FileUtil::Delete(path);
+
+ return RESULT_SUCCESS;
+}
+
+ResultCode Disk_FileSystem::RenameFile(const std::string& src_path,
+ const std::string& dest_path) const {
+ const std::string full_src_path = base_directory + src_path;
+ const std::string full_dest_path = base_directory + dest_path;
+
+ if (!FileUtil::Exists(full_src_path)) {
+ return ERROR_PATH_NOT_FOUND;
+ }
+ // TODO(wwylele): Use correct error code
+ return FileUtil::Rename(full_src_path, full_dest_path) ? RESULT_SUCCESS : ResultCode(-1);
+}
+
+ResultCode Disk_FileSystem::DeleteDirectory(const Path& path) const {
+ NGLOG_WARNING(Service_FS, "(STUBBED) called");
+ // TODO(wwylele): Use correct error code
+ return ResultCode(-1);
+}
+
+ResultCode Disk_FileSystem::DeleteDirectoryRecursively(const Path& path) const {
+ NGLOG_WARNING(Service_FS, "(STUBBED) called");
+ // TODO(wwylele): Use correct error code
+ return ResultCode(-1);
+}
+
+ResultCode Disk_FileSystem::CreateFile(const std::string& path, u64 size) const {
+ NGLOG_WARNING(Service_FS, "(STUBBED) called");
+
+ std::string full_path = base_directory + path;
+ if (size == 0) {
+ FileUtil::CreateEmptyFile(full_path);
+ return RESULT_SUCCESS;
+ }
+
+ FileUtil::IOFile file(full_path, "wb");
+ // Creates a sparse file (or a normal file on filesystems without the concept of sparse files)
+ // We do this by seeking to the right size, then writing a single null byte.
+ if (file.Seek(size - 1, SEEK_SET) && file.WriteBytes("", 1) == 1) {
+ return RESULT_SUCCESS;
+ }
+
+ NGLOG_ERROR(Service_FS, "Too large file");
+ // TODO(Subv): Find out the correct error code
+ return ResultCode(-1);
+}
+
+ResultCode Disk_FileSystem::CreateDirectory(const std::string& path) const {
+ // TODO(Subv): Perform path validation to prevent escaping the emulator sandbox.
+ std::string full_path = base_directory + path;
+
+ if (FileUtil::CreateDir(full_path)) {
+ return RESULT_SUCCESS;
+ }
+
+ NGLOG_CRITICAL(Service_FS, "(unreachable) Unknown error creating {}", full_path);
+ // TODO(wwylele): Use correct error code
+ return ResultCode(-1);
+}
+
+ResultCode Disk_FileSystem::RenameDirectory(const Path& src_path, const Path& dest_path) const {
+ NGLOG_WARNING(Service_FS, "(STUBBED) called");
+ // TODO(wwylele): Use correct error code
+ return ResultCode(-1);
+}
+
+ResultVal<std::unique_ptr<DirectoryBackend>> Disk_FileSystem::OpenDirectory(
+ const std::string& path) const {
+
+ std::string full_path = base_directory + path;
+
+ if (!FileUtil::IsDirectory(full_path)) {
+ // TODO(Subv): Find the correct error code for this.
+ return ResultCode(-1);
+ }
+
+ auto directory = std::make_unique<Disk_Directory>(full_path);
+ return MakeResult<std::unique_ptr<DirectoryBackend>>(std::move(directory));
+}
+
+u64 Disk_FileSystem::GetFreeSpaceSize() const {
+ NGLOG_WARNING(Service_FS, "(STUBBED) called");
+ return 0;
+}
+
+ResultVal<FileSys::EntryType> Disk_FileSystem::GetEntryType(const std::string& path) const {
+ std::string full_path = base_directory + path;
+ if (!FileUtil::Exists(full_path)) {
+ return ERROR_PATH_NOT_FOUND;
+ }
+
+ if (FileUtil::IsDirectory(full_path))
+ return MakeResult(EntryType::Directory);
+
+ return MakeResult(EntryType::File);
+}
+
+ResultVal<size_t> Disk_Storage::Read(const u64 offset, const size_t length, u8* buffer) const {
+ NGLOG_TRACE(Service_FS, "called offset={}, length={}", offset, length);
+ file->Seek(offset, SEEK_SET);
+ return MakeResult<size_t>(file->ReadBytes(buffer, length));
+}
+
+ResultVal<size_t> Disk_Storage::Write(const u64 offset, const size_t length, const bool flush,
+ const u8* buffer) const {
+ NGLOG_WARNING(Service_FS, "(STUBBED) called");
+ file->Seek(offset, SEEK_SET);
+ size_t written = file->WriteBytes(buffer, length);
+ if (flush) {
+ file->Flush();
+ }
+ return MakeResult<size_t>(written);
+}
+
+u64 Disk_Storage::GetSize() const {
+ return file->GetSize();
+}
+
+bool Disk_Storage::SetSize(const u64 size) const {
+ file->Resize(size);
+ file->Flush();
+ return true;
+}
+
+Disk_Directory::Disk_Directory(const std::string& path) {
+ unsigned size = FileUtil::ScanDirectoryTree(path, directory);
+ directory.size = size;
+ directory.isDirectory = true;
+ children_iterator = directory.children.begin();
+}
+
+u64 Disk_Directory::Read(const u64 count, Entry* entries) {
+ u64 entries_read = 0;
+
+ while (entries_read < count && children_iterator != directory.children.cend()) {
+ const FileUtil::FSTEntry& file = *children_iterator;
+ const std::string& filename = file.virtualName;
+ Entry& entry = entries[entries_read];
+
+ NGLOG_TRACE(Service_FS, "File {}: size={} dir={}", filename, file.size, file.isDirectory);
+
+ // TODO(Link Mauve): use a proper conversion to UTF-16.
+ for (size_t j = 0; j < FILENAME_LENGTH; ++j) {
+ entry.filename[j] = filename[j];
+ if (!filename[j])
+ break;
+ }
+
+ if (file.isDirectory) {
+ entry.file_size = 0;
+ entry.type = EntryType::Directory;
+ } else {
+ entry.file_size = file.size;
+ entry.type = EntryType::File;
+ }
+
+ ++entries_read;
+ ++children_iterator;
+ }
+ return entries_read;
+}
+
+u64 Disk_Directory::GetEntryCount() const {
+ // We convert the children iterator into a const_iterator to allow template argument deduction
+ // in std::distance.
+ std::vector<FileUtil::FSTEntry>::const_iterator current = children_iterator;
+ return std::distance(current, directory.children.end());
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/disk_filesystem.h b/src/core/file_sys/disk_filesystem.h
new file mode 100644
index 000000000..591e39fda
--- /dev/null
+++ b/src/core/file_sys/disk_filesystem.h
@@ -0,0 +1,84 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <cstddef>
+#include <memory>
+#include <string>
+#include "common/common_types.h"
+#include "common/file_util.h"
+#include "core/file_sys/directory.h"
+#include "core/file_sys/filesystem.h"
+#include "core/file_sys/storage.h"
+#include "core/hle/result.h"
+
+namespace FileSys {
+
+class Disk_FileSystem : public FileSystemBackend {
+public:
+ explicit Disk_FileSystem(std::string base_directory)
+ : base_directory(std::move(base_directory)) {}
+
+ std::string GetName() const override;
+
+ ResultVal<std::unique_ptr<StorageBackend>> OpenFile(const std::string& path,
+ Mode mode) const override;
+ ResultCode DeleteFile(const std::string& path) const override;
+ ResultCode RenameFile(const std::string& src_path, const std::string& dest_path) const override;
+ ResultCode DeleteDirectory(const Path& path) const override;
+ ResultCode DeleteDirectoryRecursively(const Path& path) const override;
+ ResultCode CreateFile(const std::string& path, u64 size) const override;
+ ResultCode CreateDirectory(const std::string& path) const override;
+ ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override;
+ ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(
+ const std::string& path) const override;
+ u64 GetFreeSpaceSize() const override;
+ ResultVal<EntryType> GetEntryType(const std::string& path) const override;
+
+protected:
+ std::string base_directory;
+};
+
+class Disk_Storage : public StorageBackend {
+public:
+ explicit Disk_Storage(std::shared_ptr<FileUtil::IOFile> file) : file(std::move(file)) {}
+
+ ResultVal<size_t> Read(u64 offset, size_t length, u8* buffer) const override;
+ ResultVal<size_t> Write(u64 offset, size_t length, bool flush, const u8* buffer) const override;
+ u64 GetSize() const override;
+ bool SetSize(u64 size) const override;
+ bool Close() const override {
+ return false;
+ }
+ void Flush() const override {}
+
+private:
+ std::shared_ptr<FileUtil::IOFile> file;
+};
+
+class Disk_Directory : public DirectoryBackend {
+public:
+ explicit Disk_Directory(const std::string& path);
+
+ ~Disk_Directory() override {
+ Close();
+ }
+
+ u64 Read(const u64 count, Entry* entries) override;
+ u64 GetEntryCount() const override;
+
+ bool Close() const override {
+ return true;
+ }
+
+protected:
+ FileUtil::FSTEntry directory;
+
+ // We need to remember the last entry we returned, so a subsequent call to Read will continue
+ // from the next one. This iterator will always point to the next unread entry.
+ std::vector<FileUtil::FSTEntry>::iterator children_iterator;
+};
+
+} // namespace FileSys
diff --git a/src/core/file_sys/errors.h b/src/core/file_sys/errors.h
index be3224ef8..0ed7d2a0c 100644
--- a/src/core/file_sys/errors.h
+++ b/src/core/file_sys/errors.h
@@ -10,36 +10,17 @@ namespace FileSys {
namespace ErrCodes {
enum {
- RomFSNotFound = 100,
- ArchiveNotMounted = 101,
- FileNotFound = 112,
- PathNotFound = 113,
- GameCardNotInserted = 141,
- NotFound = 120,
- FileAlreadyExists = 180,
- DirectoryAlreadyExists = 185,
- AlreadyExists = 190,
- InvalidOpenFlags = 230,
- DirectoryNotEmpty = 240,
- NotAFile = 250,
- NotFormatted = 340, ///< This is used by the FS service when creating a SaveData archive
- ExeFSSectionNotFound = 567,
- CommandNotAllowed = 630,
- InvalidReadFlag = 700,
- InvalidPath = 702,
- WriteBeyondEnd = 705,
- UnsupportedOpenFlags = 760,
- IncorrectExeFSReadSize = 761,
- UnexpectedFileOrDirectory = 770,
+ NotFound = 1,
};
}
+constexpr ResultCode ERROR_PATH_NOT_FOUND(ErrorModule::FS, ErrCodes::NotFound);
+
// TODO(bunnei): Replace these with correct errors for Switch OS
constexpr ResultCode ERROR_INVALID_PATH(ResultCode(-1));
constexpr ResultCode ERROR_UNSUPPORTED_OPEN_FLAGS(ResultCode(-1));
constexpr ResultCode ERROR_INVALID_OPEN_FLAGS(ResultCode(-1));
constexpr ResultCode ERROR_FILE_NOT_FOUND(ResultCode(-1));
-constexpr ResultCode ERROR_PATH_NOT_FOUND(ResultCode(-1));
constexpr ResultCode ERROR_UNEXPECTED_FILE_OR_DIRECTORY(ResultCode(-1));
constexpr ResultCode ERROR_DIRECTORY_ALREADY_EXISTS(ResultCode(-1));
constexpr ResultCode ERROR_FILE_ALREADY_EXISTS(ResultCode(-1));
diff --git a/src/core/file_sys/filesystem.cpp b/src/core/file_sys/filesystem.cpp
index 82fdb3c46..87083878b 100644
--- a/src/core/file_sys/filesystem.cpp
+++ b/src/core/file_sys/filesystem.cpp
@@ -71,7 +71,7 @@ std::string Path::AsString() const {
case Binary:
default:
// TODO(yuriks): Add assert
- LOG_ERROR(Service_FS, "LowPathType cannot be converted to string!");
+ NGLOG_ERROR(Service_FS, "LowPathType cannot be converted to string!");
return {};
}
}
@@ -87,7 +87,7 @@ std::u16string Path::AsU16Str() const {
case Invalid:
case Binary:
// TODO(yuriks): Add assert
- LOG_ERROR(Service_FS, "LowPathType cannot be converted to u16string!");
+ NGLOG_ERROR(Service_FS, "LowPathType cannot be converted to u16string!");
return {};
}
@@ -115,7 +115,7 @@ std::vector<u8> Path::AsBinary() const {
case Invalid:
default:
// TODO(yuriks): Add assert
- LOG_ERROR(Service_FS, "LowPathType cannot be converted to binary!");
+ NGLOG_ERROR(Service_FS, "LowPathType cannot be converted to binary!");
return {};
}
}
diff --git a/src/core/file_sys/filesystem.h b/src/core/file_sys/filesystem.h
index 02705506b..295a3133e 100644
--- a/src/core/file_sys/filesystem.h
+++ b/src/core/file_sys/filesystem.h
@@ -27,11 +27,15 @@ enum LowPathType : u32 {
Wchar = 4,
};
-union Mode {
- u32 hex;
- BitField<0, 1, u32> read_flag;
- BitField<1, 1, u32> write_flag;
- BitField<2, 1, u32> create_flag;
+enum EntryType : u8 {
+ Directory = 0,
+ File = 1,
+};
+
+enum class Mode : u32 {
+ Read = 1,
+ Write = 2,
+ Append = 4,
};
class Path {
@@ -86,21 +90,21 @@ public:
* @param size The size of the new file, filled with zeroes
* @return Result of the operation
*/
- virtual ResultCode CreateFile(const Path& path, u64 size) const = 0;
+ virtual ResultCode CreateFile(const std::string& path, u64 size) const = 0;
/**
* Delete a file specified by its path
* @param path Path relative to the archive
* @return Result of the operation
*/
- virtual ResultCode DeleteFile(const Path& path) const = 0;
+ virtual ResultCode DeleteFile(const std::string& path) const = 0;
/**
* Create a directory specified by its path
* @param path Path relative to the archive
* @return Result of the operation
*/
- virtual ResultCode CreateDirectory(const Path& path) const = 0;
+ virtual ResultCode CreateDirectory(const std::string& path) const = 0;
/**
* Delete a directory specified by its path
@@ -122,7 +126,8 @@ public:
* @param dest_path Destination path relative to the archive
* @return Result of the operation
*/
- virtual ResultCode RenameFile(const Path& src_path, const Path& dest_path) const = 0;
+ virtual ResultCode RenameFile(const std::string& src_path,
+ const std::string& dest_path) const = 0;
/**
* Rename a Directory specified by its path
@@ -138,21 +143,28 @@ public:
* @param mode Mode to open the file with
* @return Opened file, or error code
*/
- virtual ResultVal<std::unique_ptr<StorageBackend>> OpenFile(const Path& path,
- const Mode& mode) const = 0;
+ virtual ResultVal<std::unique_ptr<StorageBackend>> OpenFile(const std::string& path,
+ Mode mode) const = 0;
/**
* Open a directory specified by its path
* @param path Path relative to the archive
* @return Opened directory, or error code
*/
- virtual ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const = 0;
+ virtual ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(
+ const std::string& path) const = 0;
/**
* Get the free space
* @return The number of free bytes in the archive
*/
virtual u64 GetFreeSpaceSize() const = 0;
+
+ /**
+ * Get the type of the specified path
+ * @return The type of the specified path or error code
+ */
+ virtual ResultVal<EntryType> GetEntryType(const std::string& path) const = 0;
};
class FileSystemFactory : NonCopyable {
@@ -174,10 +186,9 @@ public:
/**
* Deletes the archive contents and then re-creates the base folder
* @param path Path to the archive
- * @param format_info Format information for the new archive
* @return ResultCode of the operation, 0 on success
*/
- virtual ResultCode Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info) = 0;
+ virtual ResultCode Format(const Path& path) = 0;
/**
* Retrieves the format info about the archive with the specified path
diff --git a/src/core/file_sys/partition_filesystem.cpp b/src/core/file_sys/partition_filesystem.cpp
new file mode 100644
index 000000000..808254ecc
--- /dev/null
+++ b/src/core/file_sys/partition_filesystem.cpp
@@ -0,0 +1,124 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <utility>
+#include "common/file_util.h"
+#include "common/logging/log.h"
+#include "core/file_sys/partition_filesystem.h"
+#include "core/loader/loader.h"
+
+namespace FileSys {
+
+Loader::ResultStatus PartitionFilesystem::Load(const std::string& file_path, size_t offset) {
+ FileUtil::IOFile file(file_path, "rb");
+ if (!file.IsOpen())
+ return Loader::ResultStatus::Error;
+
+ // At least be as large as the header
+ if (file.GetSize() < sizeof(Header))
+ return Loader::ResultStatus::Error;
+
+ // For cartridges, HFSs can get very large, so we need to calculate the size up to
+ // the actual content itself instead of just blindly reading in the entire file.
+ Header pfs_header;
+ if (!file.ReadBytes(&pfs_header, sizeof(Header)))
+ return Loader::ResultStatus::Error;
+
+ bool is_hfs = (memcmp(pfs_header.magic.data(), "HFS", 3) == 0);
+ size_t entry_size = is_hfs ? sizeof(HFSEntry) : sizeof(PFSEntry);
+ size_t metadata_size =
+ sizeof(Header) + (pfs_header.num_entries * entry_size) + pfs_header.strtab_size;
+
+ // Actually read in now...
+ file.Seek(offset, SEEK_SET);
+ std::vector<u8> file_data(metadata_size);
+
+ if (!file.ReadBytes(file_data.data(), metadata_size))
+ return Loader::ResultStatus::Error;
+
+ Loader::ResultStatus result = Load(file_data);
+ if (result != Loader::ResultStatus::Success)
+ NGLOG_ERROR(Service_FS, "Failed to load PFS from file {}!", file_path);
+
+ return result;
+}
+
+Loader::ResultStatus PartitionFilesystem::Load(const std::vector<u8>& file_data, size_t offset) {
+ size_t total_size = file_data.size() - offset;
+ if (total_size < sizeof(Header))
+ return Loader::ResultStatus::Error;
+
+ memcpy(&pfs_header, &file_data[offset], sizeof(Header));
+ is_hfs = (memcmp(pfs_header.magic.data(), "HFS", 3) == 0);
+
+ size_t entries_offset = offset + sizeof(Header);
+ size_t entry_size = is_hfs ? sizeof(HFSEntry) : sizeof(PFSEntry);
+ size_t strtab_offset = entries_offset + (pfs_header.num_entries * entry_size);
+ for (u16 i = 0; i < pfs_header.num_entries; i++) {
+ FileEntry entry;
+
+ memcpy(&entry.fs_entry, &file_data[entries_offset + (i * entry_size)], sizeof(FSEntry));
+ entry.name = std::string(reinterpret_cast<const char*>(
+ &file_data[strtab_offset + entry.fs_entry.strtab_offset]));
+ pfs_entries.push_back(std::move(entry));
+ }
+
+ content_offset = strtab_offset + pfs_header.strtab_size;
+
+ return Loader::ResultStatus::Success;
+}
+
+u32 PartitionFilesystem::GetNumEntries() const {
+ return pfs_header.num_entries;
+}
+
+u64 PartitionFilesystem::GetEntryOffset(int index) const {
+ if (index > GetNumEntries())
+ return 0;
+
+ return content_offset + pfs_entries[index].fs_entry.offset;
+}
+
+u64 PartitionFilesystem::GetEntrySize(int index) const {
+ if (index > GetNumEntries())
+ return 0;
+
+ return pfs_entries[index].fs_entry.size;
+}
+
+std::string PartitionFilesystem::GetEntryName(int index) const {
+ if (index > GetNumEntries())
+ return "";
+
+ return pfs_entries[index].name;
+}
+
+u64 PartitionFilesystem::GetFileOffset(const std::string& name) const {
+ for (u32 i = 0; i < pfs_header.num_entries; i++) {
+ if (pfs_entries[i].name == name)
+ return content_offset + pfs_entries[i].fs_entry.offset;
+ }
+
+ return 0;
+}
+
+u64 PartitionFilesystem::GetFileSize(const std::string& name) const {
+ for (u32 i = 0; i < pfs_header.num_entries; i++) {
+ if (pfs_entries[i].name == name)
+ return pfs_entries[i].fs_entry.size;
+ }
+
+ return 0;
+}
+
+void PartitionFilesystem::Print() const {
+ NGLOG_DEBUG(Service_FS, "Magic: {:.4}", pfs_header.magic.data());
+ NGLOG_DEBUG(Service_FS, "Files: {}", pfs_header.num_entries);
+ for (u32 i = 0; i < pfs_header.num_entries; i++) {
+ NGLOG_DEBUG(Service_FS, " > File {}: {} (0x{:X} bytes, at 0x{:X})", i,
+ pfs_entries[i].name.c_str(), pfs_entries[i].fs_entry.size,
+ GetFileOffset(pfs_entries[i].name));
+ }
+}
+} // namespace FileSys
diff --git a/src/core/file_sys/partition_filesystem.h b/src/core/file_sys/partition_filesystem.h
new file mode 100644
index 000000000..573c90057
--- /dev/null
+++ b/src/core/file_sys/partition_filesystem.h
@@ -0,0 +1,87 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <string>
+#include <vector>
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "common/swap.h"
+
+namespace Loader {
+enum class ResultStatus;
+}
+
+namespace FileSys {
+
+/**
+ * Helper which implements an interface to parse PFS/HFS filesystems.
+ * Data can either be loaded from a file path or data with an offset into it.
+ */
+class PartitionFilesystem {
+public:
+ Loader::ResultStatus Load(const std::string& file_path, size_t offset = 0);
+ Loader::ResultStatus Load(const std::vector<u8>& file_data, size_t offset = 0);
+
+ u32 GetNumEntries() const;
+ u64 GetEntryOffset(int index) const;
+ u64 GetEntrySize(int index) const;
+ std::string GetEntryName(int index) const;
+ u64 GetFileOffset(const std::string& name) const;
+ u64 GetFileSize(const std::string& name) const;
+
+ void Print() const;
+
+private:
+ struct Header {
+ std::array<char, 4> magic;
+ u32_le num_entries;
+ u32_le strtab_size;
+ INSERT_PADDING_BYTES(0x4);
+ };
+
+ static_assert(sizeof(Header) == 0x10, "PFS/HFS header structure size is wrong");
+
+#pragma pack(push, 1)
+ struct FSEntry {
+ u64_le offset;
+ u64_le size;
+ u32_le strtab_offset;
+ };
+
+ static_assert(sizeof(FSEntry) == 0x14, "FS entry structure size is wrong");
+
+ struct PFSEntry {
+ FSEntry fs_entry;
+ INSERT_PADDING_BYTES(0x4);
+ };
+
+ static_assert(sizeof(PFSEntry) == 0x18, "PFS entry structure size is wrong");
+
+ struct HFSEntry {
+ FSEntry fs_entry;
+ u32_le hash_region_size;
+ INSERT_PADDING_BYTES(0x8);
+ std::array<char, 0x20> hash;
+ };
+
+ static_assert(sizeof(HFSEntry) == 0x40, "HFS entry structure size is wrong");
+
+#pragma pack(pop)
+
+ struct FileEntry {
+ FSEntry fs_entry;
+ std::string name;
+ };
+
+ Header pfs_header;
+ bool is_hfs;
+ size_t content_offset;
+
+ std::vector<FileEntry> pfs_entries;
+};
+
+} // namespace FileSys
diff --git a/src/core/file_sys/program_metadata.cpp b/src/core/file_sys/program_metadata.cpp
index a6dcebcc3..25a822891 100644
--- a/src/core/file_sys/program_metadata.cpp
+++ b/src/core/file_sys/program_metadata.cpp
@@ -2,7 +2,6 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include <cinttypes>
#include "common/file_util.h"
#include "common/logging/log.h"
#include "core/file_sys/program_metadata.h"
@@ -22,7 +21,7 @@ Loader::ResultStatus ProgramMetadata::Load(const std::string& file_path) {
Loader::ResultStatus result = Load(file_data);
if (result != Loader::ResultStatus::Success)
- LOG_ERROR(Service_FS, "Failed to load NPDM from file %s!", file_path.c_str());
+ NGLOG_ERROR(Service_FS, "Failed to load NPDM from file {}!", file_path);
return result;
}
@@ -77,14 +76,14 @@ u64 ProgramMetadata::GetFilesystemPermissions() const {
}
void ProgramMetadata::Print() const {
- LOG_DEBUG(Service_FS, "Magic: %.4s", npdm_header.magic.data());
- LOG_DEBUG(Service_FS, "Main thread priority: 0x%02x", npdm_header.main_thread_priority);
- LOG_DEBUG(Service_FS, "Main thread core: %u", npdm_header.main_thread_cpu);
- LOG_DEBUG(Service_FS, "Main thread stack size: 0x%x bytes", npdm_header.main_stack_size);
- LOG_DEBUG(Service_FS, "Process category: %u", npdm_header.process_category);
- LOG_DEBUG(Service_FS, "Flags: %02x", npdm_header.flags);
- LOG_DEBUG(Service_FS, " > 64-bit instructions: %s",
- npdm_header.has_64_bit_instructions ? "YES" : "NO");
+ NGLOG_DEBUG(Service_FS, "Magic: {:.4}", npdm_header.magic.data());
+ NGLOG_DEBUG(Service_FS, "Main thread priority: 0x{:02X}", npdm_header.main_thread_priority);
+ NGLOG_DEBUG(Service_FS, "Main thread core: {}", npdm_header.main_thread_cpu);
+ NGLOG_DEBUG(Service_FS, "Main thread stack size: 0x{:X} bytes", npdm_header.main_stack_size);
+ NGLOG_DEBUG(Service_FS, "Process category: {}", npdm_header.process_category);
+ NGLOG_DEBUG(Service_FS, "Flags: 0x{:02X}", npdm_header.flags);
+ NGLOG_DEBUG(Service_FS, " > 64-bit instructions: {}",
+ npdm_header.has_64_bit_instructions ? "YES" : "NO");
auto address_space = "Unknown";
switch (npdm_header.address_space_type) {
@@ -96,19 +95,19 @@ void ProgramMetadata::Print() const {
break;
}
- LOG_DEBUG(Service_FS, " > Address space: %s\n", address_space);
+ NGLOG_DEBUG(Service_FS, " > Address space: {}\n", address_space);
// Begin ACID printing (potential perms, signed)
- LOG_DEBUG(Service_FS, "Magic: %.4s", acid_header.magic.data());
- LOG_DEBUG(Service_FS, "Flags: %02x", acid_header.flags);
- LOG_DEBUG(Service_FS, " > Is Retail: %s", acid_header.is_retail ? "YES" : "NO");
- LOG_DEBUG(Service_FS, "Title ID Min: %016" PRIX64, acid_header.title_id_min);
- LOG_DEBUG(Service_FS, "Title ID Max: %016" PRIX64, acid_header.title_id_max);
- LOG_DEBUG(Service_FS, "Filesystem Access: %016" PRIX64 "\n", acid_file_access.permissions);
+ NGLOG_DEBUG(Service_FS, "Magic: {:.4}", acid_header.magic.data());
+ NGLOG_DEBUG(Service_FS, "Flags: 0x{:02X}", acid_header.flags);
+ NGLOG_DEBUG(Service_FS, " > Is Retail: {}", acid_header.is_retail ? "YES" : "NO");
+ NGLOG_DEBUG(Service_FS, "Title ID Min: 0x{:016X}", acid_header.title_id_min);
+ NGLOG_DEBUG(Service_FS, "Title ID Max: 0x{:016X}", acid_header.title_id_max);
+ NGLOG_DEBUG(Service_FS, "Filesystem Access: 0x{:016X}\n", acid_file_access.permissions);
// Begin ACI0 printing (actual perms, unsigned)
- LOG_DEBUG(Service_FS, "Magic: %.4s", aci_header.magic.data());
- LOG_DEBUG(Service_FS, "Title ID: %016" PRIX64, aci_header.title_id);
- LOG_DEBUG(Service_FS, "Filesystem Access: %016" PRIX64 "\n", aci_file_access.permissions);
+ NGLOG_DEBUG(Service_FS, "Magic: {:.4}", aci_header.magic.data());
+ NGLOG_DEBUG(Service_FS, "Title ID: 0x{:016X}", aci_header.title_id);
+ NGLOG_DEBUG(Service_FS, "Filesystem Access: 0x{:016X}\n", aci_file_access.permissions);
}
} // namespace FileSys
diff --git a/src/core/file_sys/romfs_factory.cpp b/src/core/file_sys/romfs_factory.cpp
index e0de49f05..dc7591aca 100644
--- a/src/core/file_sys/romfs_factory.cpp
+++ b/src/core/file_sys/romfs_factory.cpp
@@ -14,7 +14,7 @@ namespace FileSys {
RomFS_Factory::RomFS_Factory(Loader::AppLoader& app_loader) {
// Load the RomFS from the app
if (Loader::ResultStatus::Success != app_loader.ReadRomFS(romfs_file, data_offset, data_size)) {
- LOG_ERROR(Service_FS, "Unable to read RomFS!");
+ NGLOG_ERROR(Service_FS, "Unable to read RomFS!");
}
}
@@ -23,14 +23,14 @@ ResultVal<std::unique_ptr<FileSystemBackend>> RomFS_Factory::Open(const Path& pa
return MakeResult<std::unique_ptr<FileSystemBackend>>(std::move(archive));
}
-ResultCode RomFS_Factory::Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info) {
- LOG_ERROR(Service_FS, "Unimplemented Format archive %s", GetName().c_str());
+ResultCode RomFS_Factory::Format(const Path& path) {
+ NGLOG_ERROR(Service_FS, "Unimplemented Format archive {}", GetName());
// TODO(bunnei): Find the right error code for this
return ResultCode(-1);
}
ResultVal<ArchiveFormatInfo> RomFS_Factory::GetFormatInfo(const Path& path) const {
- LOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive %s", GetName().c_str());
+ NGLOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive {}", GetName());
// TODO(bunnei): Find the right error code for this
return ResultCode(-1);
}
diff --git a/src/core/file_sys/romfs_factory.h b/src/core/file_sys/romfs_factory.h
index 10ea13966..e0698e642 100644
--- a/src/core/file_sys/romfs_factory.h
+++ b/src/core/file_sys/romfs_factory.h
@@ -23,7 +23,7 @@ public:
return "ArchiveFactory_RomFS";
}
ResultVal<std::unique_ptr<FileSystemBackend>> Open(const Path& path) override;
- ResultCode Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info) override;
+ ResultCode Format(const Path& path) override;
ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path) const override;
private:
diff --git a/src/core/file_sys/romfs_filesystem.cpp b/src/core/file_sys/romfs_filesystem.cpp
index ca1463d7c..8e2bce687 100644
--- a/src/core/file_sys/romfs_filesystem.cpp
+++ b/src/core/file_sys/romfs_filesystem.cpp
@@ -14,73 +14,79 @@ std::string RomFS_FileSystem::GetName() const {
return "RomFS";
}
-ResultVal<std::unique_ptr<StorageBackend>> RomFS_FileSystem::OpenFile(const Path& path,
- const Mode& mode) const {
+ResultVal<std::unique_ptr<StorageBackend>> RomFS_FileSystem::OpenFile(const std::string& path,
+ Mode mode) const {
return MakeResult<std::unique_ptr<StorageBackend>>(
std::make_unique<RomFS_Storage>(romfs_file, data_offset, data_size));
}
-ResultCode RomFS_FileSystem::DeleteFile(const Path& path) const {
- LOG_CRITICAL(Service_FS, "Attempted to delete a file from an ROMFS archive (%s).",
- GetName().c_str());
+ResultCode RomFS_FileSystem::DeleteFile(const std::string& path) const {
+ NGLOG_CRITICAL(Service_FS, "Attempted to delete a file from an ROMFS archive ({}).", GetName());
// TODO(bunnei): Use correct error code
return ResultCode(-1);
}
-ResultCode RomFS_FileSystem::RenameFile(const Path& src_path, const Path& dest_path) const {
- LOG_CRITICAL(Service_FS, "Attempted to rename a file within an ROMFS archive (%s).",
- GetName().c_str());
+ResultCode RomFS_FileSystem::RenameFile(const std::string& src_path,
+ const std::string& dest_path) const {
+ NGLOG_CRITICAL(Service_FS, "Attempted to rename a file within an ROMFS archive ({}).",
+ GetName());
// TODO(wwylele): Use correct error code
return ResultCode(-1);
}
ResultCode RomFS_FileSystem::DeleteDirectory(const Path& path) const {
- LOG_CRITICAL(Service_FS, "Attempted to delete a directory from an ROMFS archive (%s).",
- GetName().c_str());
+ NGLOG_CRITICAL(Service_FS, "Attempted to delete a directory from an ROMFS archive ({}).",
+ GetName());
// TODO(wwylele): Use correct error code
return ResultCode(-1);
}
ResultCode RomFS_FileSystem::DeleteDirectoryRecursively(const Path& path) const {
- LOG_CRITICAL(Service_FS, "Attempted to delete a directory from an ROMFS archive (%s).",
- GetName().c_str());
+ NGLOG_CRITICAL(Service_FS, "Attempted to delete a directory from an ROMFS archive ({}).",
+ GetName());
// TODO(wwylele): Use correct error code
return ResultCode(-1);
}
-ResultCode RomFS_FileSystem::CreateFile(const Path& path, u64 size) const {
- LOG_CRITICAL(Service_FS, "Attempted to create a file in an ROMFS archive (%s).",
- GetName().c_str());
+ResultCode RomFS_FileSystem::CreateFile(const std::string& path, u64 size) const {
+ NGLOG_CRITICAL(Service_FS, "Attempted to create a file in an ROMFS archive ({}).", GetName());
// TODO(bunnei): Use correct error code
return ResultCode(-1);
}
-ResultCode RomFS_FileSystem::CreateDirectory(const Path& path) const {
- LOG_CRITICAL(Service_FS, "Attempted to create a directory in an ROMFS archive (%s).",
- GetName().c_str());
+ResultCode RomFS_FileSystem::CreateDirectory(const std::string& path) const {
+ NGLOG_CRITICAL(Service_FS, "Attempted to create a directory in an ROMFS archive ({}).",
+ GetName());
// TODO(wwylele): Use correct error code
return ResultCode(-1);
}
ResultCode RomFS_FileSystem::RenameDirectory(const Path& src_path, const Path& dest_path) const {
- LOG_CRITICAL(Service_FS, "Attempted to rename a file within an ROMFS archive (%s).",
- GetName().c_str());
+ NGLOG_CRITICAL(Service_FS, "Attempted to rename a file within an ROMFS archive ({}).",
+ GetName());
// TODO(wwylele): Use correct error code
return ResultCode(-1);
}
ResultVal<std::unique_ptr<DirectoryBackend>> RomFS_FileSystem::OpenDirectory(
- const Path& path) const {
+ const std::string& path) const {
+ NGLOG_WARNING(Service_FS, "Opening Directory in a ROMFS archive");
return MakeResult<std::unique_ptr<DirectoryBackend>>(std::make_unique<ROMFSDirectory>());
}
u64 RomFS_FileSystem::GetFreeSpaceSize() const {
- LOG_WARNING(Service_FS, "Attempted to get the free space in an ROMFS archive");
+ NGLOG_WARNING(Service_FS, "Attempted to get the free space in an ROMFS archive");
return 0;
}
+ResultVal<FileSys::EntryType> RomFS_FileSystem::GetEntryType(const std::string& path) const {
+ NGLOG_CRITICAL(Service_FS, "Called within an ROMFS archive (path {}).", path);
+ // TODO(wwylele): Use correct error code
+ return ResultCode(-1);
+}
+
ResultVal<size_t> RomFS_Storage::Read(const u64 offset, const size_t length, u8* buffer) const {
- LOG_TRACE(Service_FS, "called offset=%llu, length=%zu", offset, length);
+ NGLOG_TRACE(Service_FS, "called offset={}, length={}", offset, length);
romfs_file->Seek(data_offset + offset, SEEK_SET);
size_t read_length = (size_t)std::min((u64)length, data_size - offset);
@@ -89,7 +95,7 @@ ResultVal<size_t> RomFS_Storage::Read(const u64 offset, const size_t length, u8*
ResultVal<size_t> RomFS_Storage::Write(const u64 offset, const size_t length, const bool flush,
const u8* buffer) const {
- LOG_ERROR(Service_FS, "Attempted to write to ROMFS file");
+ NGLOG_ERROR(Service_FS, "Attempted to write to ROMFS file");
// TODO(Subv): Find error code
return MakeResult<size_t>(0);
}
@@ -99,7 +105,7 @@ u64 RomFS_Storage::GetSize() const {
}
bool RomFS_Storage::SetSize(const u64 size) const {
- LOG_ERROR(Service_FS, "Attempted to set the size of an ROMFS file");
+ NGLOG_ERROR(Service_FS, "Attempted to set the size of an ROMFS file");
return false;
}
diff --git a/src/core/file_sys/romfs_filesystem.h b/src/core/file_sys/romfs_filesystem.h
index 900ea567a..ba9d85823 100644
--- a/src/core/file_sys/romfs_filesystem.h
+++ b/src/core/file_sys/romfs_filesystem.h
@@ -29,17 +29,19 @@ public:
std::string GetName() const override;
- ResultVal<std::unique_ptr<StorageBackend>> OpenFile(const Path& path,
- const Mode& mode) const override;
- ResultCode DeleteFile(const Path& path) const override;
- ResultCode RenameFile(const Path& src_path, const Path& dest_path) const override;
+ ResultVal<std::unique_ptr<StorageBackend>> OpenFile(const std::string& path,
+ Mode mode) const override;
+ ResultCode DeleteFile(const std::string& path) const override;
+ ResultCode RenameFile(const std::string& src_path, const std::string& dest_path) const override;
ResultCode DeleteDirectory(const Path& path) const override;
ResultCode DeleteDirectoryRecursively(const Path& path) const override;
- ResultCode CreateFile(const Path& path, u64 size) const override;
- ResultCode CreateDirectory(const Path& path) const override;
+ ResultCode CreateFile(const std::string& path, u64 size) const override;
+ ResultCode CreateDirectory(const std::string& path) const override;
ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override;
- ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override;
+ ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(
+ const std::string& path) const override;
u64 GetFreeSpaceSize() const override;
+ ResultVal<EntryType> GetEntryType(const std::string& path) const override;
protected:
std::shared_ptr<FileUtil::IOFile> romfs_file;
@@ -69,7 +71,10 @@ private:
class ROMFSDirectory : public DirectoryBackend {
public:
- u32 Read(const u32 count, Entry* entries) override {
+ u64 Read(const u64 count, Entry* entries) override {
+ return 0;
+ }
+ u64 GetEntryCount() const override {
return 0;
}
bool Close() const override {
diff --git a/src/core/file_sys/savedata_factory.cpp b/src/core/file_sys/savedata_factory.cpp
new file mode 100644
index 000000000..c1be8fee4
--- /dev/null
+++ b/src/core/file_sys/savedata_factory.cpp
@@ -0,0 +1,54 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <memory>
+#include "common/common_types.h"
+#include "common/logging/log.h"
+#include "core/core.h"
+#include "core/file_sys/disk_filesystem.h"
+#include "core/file_sys/savedata_factory.h"
+#include "core/hle/kernel/process.h"
+
+namespace FileSys {
+
+SaveData_Factory::SaveData_Factory(std::string nand_directory)
+ : nand_directory(std::move(nand_directory)) {}
+
+ResultVal<std::unique_ptr<FileSystemBackend>> SaveData_Factory::Open(const Path& path) {
+ std::string save_directory = GetFullPath();
+ // Return an error if the save data doesn't actually exist.
+ if (!FileUtil::IsDirectory(save_directory)) {
+ // TODO(Subv): Find out correct error code.
+ return ResultCode(-1);
+ }
+
+ auto archive = std::make_unique<Disk_FileSystem>(save_directory);
+ return MakeResult<std::unique_ptr<FileSystemBackend>>(std::move(archive));
+}
+
+ResultCode SaveData_Factory::Format(const Path& path) {
+ NGLOG_WARNING(Service_FS, "Format archive {}", GetName());
+ // Create the save data directory.
+ if (!FileUtil::CreateFullPath(GetFullPath())) {
+ // TODO(Subv): Find the correct error code.
+ return ResultCode(-1);
+ }
+
+ return RESULT_SUCCESS;
+}
+
+ResultVal<ArchiveFormatInfo> SaveData_Factory::GetFormatInfo(const Path& path) const {
+ NGLOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive {}", GetName());
+ // TODO(bunnei): Find the right error code for this
+ return ResultCode(-1);
+}
+
+std::string SaveData_Factory::GetFullPath() const {
+ u64 title_id = Core::CurrentProcess()->program_id;
+ // TODO(Subv): Somehow obtain this value.
+ u32 user = 0;
+ return fmt::format("{}save/{:016X}/{:08X}/", nand_directory, title_id, user);
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/savedata_factory.h b/src/core/file_sys/savedata_factory.h
new file mode 100644
index 000000000..73a42aab6
--- /dev/null
+++ b/src/core/file_sys/savedata_factory.h
@@ -0,0 +1,33 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <string>
+#include "common/common_types.h"
+#include "core/file_sys/filesystem.h"
+#include "core/hle/result.h"
+
+namespace FileSys {
+
+/// File system interface to the SaveData archive
+class SaveData_Factory final : public FileSystemFactory {
+public:
+ explicit SaveData_Factory(std::string nand_directory);
+
+ std::string GetName() const override {
+ return "SaveData_Factory";
+ }
+ ResultVal<std::unique_ptr<FileSystemBackend>> Open(const Path& path) override;
+ ResultCode Format(const Path& path) override;
+ ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path) const override;
+
+private:
+ std::string nand_directory;
+
+ std::string GetFullPath() const;
+};
+
+} // namespace FileSys
diff --git a/src/core/file_sys/sdmc_factory.cpp b/src/core/file_sys/sdmc_factory.cpp
new file mode 100644
index 000000000..59ac3e0be
--- /dev/null
+++ b/src/core/file_sys/sdmc_factory.cpp
@@ -0,0 +1,39 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <memory>
+#include "common/common_types.h"
+#include "common/logging/log.h"
+#include "common/string_util.h"
+#include "core/core.h"
+#include "core/file_sys/disk_filesystem.h"
+#include "core/file_sys/sdmc_factory.h"
+
+namespace FileSys {
+
+SDMC_Factory::SDMC_Factory(std::string sd_directory) : sd_directory(std::move(sd_directory)) {}
+
+ResultVal<std::unique_ptr<FileSystemBackend>> SDMC_Factory::Open(const Path& path) {
+ // Create the SD Card directory if it doesn't already exist.
+ if (!FileUtil::IsDirectory(sd_directory)) {
+ FileUtil::CreateFullPath(sd_directory);
+ }
+
+ auto archive = std::make_unique<Disk_FileSystem>(sd_directory);
+ return MakeResult<std::unique_ptr<FileSystemBackend>>(std::move(archive));
+}
+
+ResultCode SDMC_Factory::Format(const Path& path) {
+ NGLOG_ERROR(Service_FS, "Unimplemented Format archive {}", GetName());
+ // TODO(Subv): Find the right error code for this
+ return ResultCode(-1);
+}
+
+ResultVal<ArchiveFormatInfo> SDMC_Factory::GetFormatInfo(const Path& path) const {
+ NGLOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive {}", GetName());
+ // TODO(bunnei): Find the right error code for this
+ return ResultCode(-1);
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/sdmc_factory.h b/src/core/file_sys/sdmc_factory.h
new file mode 100644
index 000000000..93becda25
--- /dev/null
+++ b/src/core/file_sys/sdmc_factory.h
@@ -0,0 +1,31 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <string>
+#include "common/common_types.h"
+#include "core/file_sys/filesystem.h"
+#include "core/hle/result.h"
+
+namespace FileSys {
+
+/// File system interface to the SDCard archive
+class SDMC_Factory final : public FileSystemFactory {
+public:
+ explicit SDMC_Factory(std::string sd_directory);
+
+ std::string GetName() const override {
+ return "SDMC_Factory";
+ }
+ ResultVal<std::unique_ptr<FileSystemBackend>> Open(const Path& path) override;
+ ResultCode Format(const Path& path) override;
+ ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path) const override;
+
+private:
+ std::string sd_directory;
+};
+
+} // namespace FileSys
diff --git a/src/core/frontend/input.h b/src/core/frontend/input.h
index 8c256beb5..79e52488f 100644
--- a/src/core/frontend/input.h
+++ b/src/core/frontend/input.h
@@ -59,7 +59,7 @@ template <typename InputDeviceType>
void RegisterFactory(const std::string& name, std::shared_ptr<Factory<InputDeviceType>> factory) {
auto pair = std::make_pair(name, std::move(factory));
if (!Impl::FactoryList<InputDeviceType>::list.insert(std::move(pair)).second) {
- LOG_ERROR(Input, "Factory %s already registered", name.c_str());
+ NGLOG_ERROR(Input, "Factory '{}' already registered", name);
}
}
@@ -71,7 +71,7 @@ void RegisterFactory(const std::string& name, std::shared_ptr<Factory<InputDevic
template <typename InputDeviceType>
void UnregisterFactory(const std::string& name) {
if (Impl::FactoryList<InputDeviceType>::list.erase(name) == 0) {
- LOG_ERROR(Input, "Factory %s not registered", name.c_str());
+ NGLOG_ERROR(Input, "Factory '{}' not registered", name);
}
}
@@ -88,7 +88,7 @@ std::unique_ptr<InputDeviceType> CreateDevice(const std::string& params) {
const auto pair = factory_list.find(engine);
if (pair == factory_list.end()) {
if (engine != "null") {
- LOG_ERROR(Input, "Unknown engine name: %s", engine.c_str());
+ NGLOG_ERROR(Input, "Unknown engine name: {}", engine);
}
return std::make_unique<InputDeviceType>();
}
diff --git a/src/core/gdbstub/gdbstub.cpp b/src/core/gdbstub/gdbstub.cpp
index 7a142dc21..2603192fe 100644
--- a/src/core/gdbstub/gdbstub.cpp
+++ b/src/core/gdbstub/gdbstub.cpp
@@ -6,7 +6,6 @@
#include <algorithm>
#include <atomic>
-#include <cinttypes>
#include <climits>
#include <csignal>
#include <cstdarg>
@@ -33,9 +32,13 @@
#include "common/logging/log.h"
#include "common/string_util.h"
+#include "common/swap.h"
#include "core/arm/arm_interface.h"
#include "core/core.h"
+#include "core/core_cpu.h"
#include "core/gdbstub/gdbstub.h"
+#include "core/hle/kernel/kernel.h"
+#include "core/hle/kernel/scheduler.h"
#include "core/loader/loader.h"
#include "core/memory.h"
@@ -138,15 +141,17 @@ static u8 command_buffer[GDB_BUFFER_SIZE];
static u32 command_length;
static u32 latest_signal = 0;
-static bool step_break = false;
static bool memory_break = false;
+static Kernel::Thread* current_thread = nullptr;
+
// Binding to a port within the reserved ports range (0-1023) requires root permissions,
// so default to a port outside of that range.
static u16 gdbstub_port = 24689;
static bool halt_loop = true;
static bool step_loop = false;
+static bool send_trap = false;
// If set to false, the server will never be started and no
// gdbstub-related functions will be executed.
@@ -166,6 +171,53 @@ static std::map<u64, Breakpoint> breakpoints_execute;
static std::map<u64, Breakpoint> breakpoints_read;
static std::map<u64, Breakpoint> breakpoints_write;
+static Kernel::Thread* FindThreadById(int id) {
+ for (int core = 0; core < Core::NUM_CPU_CORES; core++) {
+ auto threads = Core::System::GetInstance().Scheduler(core)->GetThreadList();
+ for (auto thread : threads) {
+ if (thread->GetThreadId() == id) {
+ current_thread = thread.get();
+ return current_thread;
+ }
+ }
+ }
+ return nullptr;
+}
+
+static u64 RegRead(int id, Kernel::Thread* thread = nullptr) {
+ if (!thread) {
+ return 0;
+ }
+
+ if (id < SP_REGISTER) {
+ return thread->context.cpu_registers[id];
+ } else if (id == SP_REGISTER) {
+ return thread->context.sp;
+ } else if (id == PC_REGISTER) {
+ return thread->context.pc;
+ } else if (id == CPSR_REGISTER) {
+ return thread->context.cpsr;
+ } else {
+ return 0;
+ }
+}
+
+static void RegWrite(int id, u64 val, Kernel::Thread* thread = nullptr) {
+ if (!thread) {
+ return;
+ }
+
+ if (id < SP_REGISTER) {
+ thread->context.cpu_registers[id] = val;
+ } else if (id == SP_REGISTER) {
+ thread->context.sp = val;
+ } else if (id == PC_REGISTER) {
+ thread->context.pc = val;
+ } else if (id == CPSR_REGISTER) {
+ thread->context.cpsr = val;
+ }
+}
+
/**
* Turns hex string character into the equivalent byte.
*
@@ -180,7 +232,7 @@ static u8 HexCharToValue(u8 hex) {
return hex - 'A' + 0xA;
}
- LOG_ERROR(Debug_GDBStub, "Invalid nibble: %c (%02x)\n", hex, hex);
+ NGLOG_ERROR(Debug_GDBStub, "Invalid nibble: {} ({:02X})", hex, hex);
return 0;
}
@@ -194,7 +246,7 @@ static u8 NibbleToHex(u8 n) {
if (n < 0xA) {
return '0' + n;
} else {
- return 'A' + n - 0xA;
+ return 'a' + n - 0xA;
}
}
@@ -320,7 +372,7 @@ static u8 ReadByte() {
u8 c;
size_t received_size = recv(gdbserver_socket, reinterpret_cast<char*>(&c), 1, MSG_WAITALL);
if (received_size != 1) {
- LOG_ERROR(Debug_GDBStub, "recv failed : %ld", received_size);
+ NGLOG_ERROR(Debug_GDBStub, "recv failed: {}", received_size);
Shutdown();
}
@@ -361,9 +413,8 @@ static void RemoveBreakpoint(BreakpointType type, PAddr addr) {
auto bp = p.find(static_cast<u64>(addr));
if (bp != p.end()) {
- LOG_DEBUG(Debug_GDBStub,
- "gdb: removed a breakpoint: %016" PRIx64 " bytes at %016" PRIx64 " of type %d\n",
- bp->second.len, bp->second.addr, static_cast<int>(type));
+ NGLOG_DEBUG(Debug_GDBStub, "gdb: removed a breakpoint: {:016X} bytes at {:016X} of type {}",
+ bp->second.len, bp->second.addr, static_cast<int>(type));
p.erase(static_cast<u64>(addr));
}
}
@@ -408,10 +459,10 @@ bool CheckBreakpoint(PAddr addr, BreakpointType type) {
}
if (bp->second.active && (addr >= bp->second.addr && addr < bp->second.addr + len)) {
- LOG_DEBUG(Debug_GDBStub,
- "Found breakpoint type %d @ %016" PRIx64 ", range: %016" PRIx64
- " - %016" PRIx64 " (%" PRIx64 " bytes)\n",
- static_cast<int>(type), addr, bp->second.addr, bp->second.addr + len, len);
+ NGLOG_DEBUG(Debug_GDBStub,
+ "Found breakpoint type {} @ {:016X}, range: {:016X}"
+ " - {:016X} ({:X} bytes)",
+ static_cast<int>(type), addr, bp->second.addr, bp->second.addr + len, len);
return true;
}
}
@@ -427,7 +478,7 @@ bool CheckBreakpoint(PAddr addr, BreakpointType type) {
static void SendPacket(const char packet) {
size_t sent_size = send(gdbserver_socket, &packet, 1, 0);
if (sent_size != 1) {
- LOG_ERROR(Debug_GDBStub, "send failed");
+ NGLOG_ERROR(Debug_GDBStub, "send failed");
}
}
@@ -441,11 +492,13 @@ static void SendReply(const char* reply) {
return;
}
+ NGLOG_DEBUG(Debug_GDBStub, "Reply: {}", reply);
+
memset(command_buffer, 0, sizeof(command_buffer));
command_length = static_cast<u32>(strlen(reply));
if (command_length + 4 > sizeof(command_buffer)) {
- LOG_ERROR(Debug_GDBStub, "command_buffer overflow in SendReply");
+ NGLOG_ERROR(Debug_GDBStub, "command_buffer overflow in SendReply");
return;
}
@@ -462,7 +515,7 @@ static void SendReply(const char* reply) {
while (left > 0) {
int sent_size = send(gdbserver_socket, reinterpret_cast<char*>(ptr), left, 0);
if (sent_size < 0) {
- LOG_ERROR(Debug_GDBStub, "gdb: send failed");
+ NGLOG_ERROR(Debug_GDBStub, "gdb: send failed");
return Shutdown();
}
@@ -473,7 +526,7 @@ static void SendReply(const char* reply) {
/// Handle query command from gdb client.
static void HandleQuery() {
- LOG_DEBUG(Debug_GDBStub, "gdb: query '%s'\n", command_buffer + 1);
+ NGLOG_DEBUG(Debug_GDBStub, "gdb: query '{}'", command_buffer + 1);
const char* query = reinterpret_cast<const char*>(command_buffer + 1);
@@ -485,6 +538,22 @@ static void HandleQuery() {
} else if (strncmp(query, "Xfer:features:read:target.xml:",
strlen("Xfer:features:read:target.xml:")) == 0) {
SendReply(target_xml);
+ } else if (strncmp(query, "Offsets", strlen("Offsets")) == 0) {
+ std::string buffer = fmt::format("TextSeg={:0x}", Memory::PROCESS_IMAGE_VADDR);
+ SendReply(buffer.c_str());
+ } else if (strncmp(query, "fThreadInfo", strlen("fThreadInfo")) == 0) {
+ std::string val = "m";
+ for (int core = 0; core < Core::NUM_CPU_CORES; core++) {
+ auto threads = Core::System::GetInstance().Scheduler(core)->GetThreadList();
+ for (auto thread : threads) {
+ val += fmt::format("{:x}", thread->GetThreadId());
+ val += ",";
+ }
+ }
+ val.pop_back();
+ SendReply(val.c_str());
+ } else if (strncmp(query, "sThreadInfo", strlen("sThreadInfo")) == 0) {
+ SendReply("l");
} else {
SendReply("");
}
@@ -492,11 +561,40 @@ static void HandleQuery() {
/// Handle set thread command from gdb client.
static void HandleSetThread() {
- if (memcmp(command_buffer, "Hg0", 3) == 0 || memcmp(command_buffer, "Hc-1", 4) == 0 ||
- memcmp(command_buffer, "Hc0", 4) == 0 || memcmp(command_buffer, "Hc1", 4) == 0) {
- return SendReply("OK");
+ if (memcmp(command_buffer, "Hc", 2) == 0 || memcmp(command_buffer, "Hg", 2) == 0) {
+ int thread_id = -1;
+ if (command_buffer[2] != '-') {
+ thread_id = static_cast<int>(HexToInt(
+ command_buffer + 2,
+ command_length - 2 /*strlen(reinterpret_cast<char*>(command_buffer) + 2)*/));
+ }
+ if (thread_id >= 1) {
+ current_thread = FindThreadById(thread_id);
+ }
+ if (!current_thread) {
+ thread_id = 1;
+ current_thread = FindThreadById(thread_id);
+ }
+ if (current_thread) {
+ SendReply("OK");
+ return;
+ }
}
+ SendReply("E01");
+}
+/// Handle thread alive command from gdb client.
+static void HandleThreadAlive() {
+ int thread_id = static_cast<int>(
+ HexToInt(command_buffer + 1,
+ command_length - 1 /*strlen(reinterpret_cast<char*>(command_buffer) + 1)*/));
+ if (thread_id == 0) {
+ thread_id = 1;
+ }
+ if (FindThreadById(thread_id)) {
+ SendReply("OK");
+ return;
+ }
SendReply("E01");
}
@@ -505,15 +603,24 @@ static void HandleSetThread() {
*
* @param signal Signal to be sent to client.
*/
-static void SendSignal(u32 signal) {
+static void SendSignal(Kernel::Thread* thread, u32 signal, bool full = true) {
if (gdbserver_socket == -1) {
return;
}
latest_signal = signal;
- std::string buffer = Common::StringFromFormat("T%02x", latest_signal);
- LOG_DEBUG(Debug_GDBStub, "Response: %s", buffer.c_str());
+ std::string buffer;
+ if (full) {
+ buffer = fmt::format("T{:02x}{:02x}:{:016x};{:02x}:{:016x};", latest_signal, PC_REGISTER,
+ Common::swap64(RegRead(PC_REGISTER, thread)), SP_REGISTER,
+ Common::swap64(RegRead(SP_REGISTER, thread)));
+ } else {
+ buffer = fmt::format("T{:02x};", latest_signal);
+ }
+
+ buffer += fmt::format("thread:{:x};", thread->GetThreadId());
+
SendReply(buffer.c_str());
}
@@ -527,18 +634,18 @@ static void ReadCommand() {
// ignore ack
return;
} else if (c == 0x03) {
- LOG_INFO(Debug_GDBStub, "gdb: found break command\n");
+ NGLOG_INFO(Debug_GDBStub, "gdb: found break command");
halt_loop = true;
- SendSignal(SIGTRAP);
+ SendSignal(current_thread, SIGTRAP);
return;
} else if (c != GDB_STUB_START) {
- LOG_DEBUG(Debug_GDBStub, "gdb: read invalid byte %02x\n", c);
+ NGLOG_DEBUG(Debug_GDBStub, "gdb: read invalid byte {:02X}", c);
return;
}
while ((c = ReadByte()) != GDB_STUB_END) {
if (command_length >= sizeof(command_buffer)) {
- LOG_ERROR(Debug_GDBStub, "gdb: command_buffer overflow\n");
+ NGLOG_ERROR(Debug_GDBStub, "gdb: command_buffer overflow");
SendPacket(GDB_STUB_NACK);
return;
}
@@ -551,9 +658,10 @@ static void ReadCommand() {
u8 checksum_calculated = CalculateChecksum(command_buffer, command_length);
if (checksum_received != checksum_calculated) {
- LOG_ERROR(Debug_GDBStub,
- "gdb: invalid checksum: calculated %02x and read %02x for $%s# (length: %d)\n",
- checksum_calculated, checksum_received, command_buffer, command_length);
+ NGLOG_ERROR(
+ Debug_GDBStub,
+ "gdb: invalid checksum: calculated {:02X} and read {:02X} for ${}# (length: {})",
+ checksum_calculated, checksum_received, command_buffer, command_length);
command_length = 0;
@@ -580,7 +688,7 @@ static bool IsDataAvailable() {
t.tv_usec = 0;
if (select(gdbserver_socket + 1, &fd_socket, nullptr, nullptr, &t) < 0) {
- LOG_ERROR(Debug_GDBStub, "select failed");
+ NGLOG_ERROR(Debug_GDBStub, "select failed");
return false;
}
@@ -599,11 +707,11 @@ static void ReadRegister() {
}
if (id <= SP_REGISTER) {
- LongToGdbHex(reply, Core::CPU().GetReg(static_cast<int>(id)));
+ LongToGdbHex(reply, RegRead(id, current_thread));
} else if (id == PC_REGISTER) {
- LongToGdbHex(reply, Core::CPU().GetPC());
+ LongToGdbHex(reply, RegRead(id, current_thread));
} else if (id == CPSR_REGISTER) {
- IntToGdbHex(reply, Core::CPU().GetCPSR());
+ IntToGdbHex(reply, (u32)RegRead(id, current_thread));
} else {
return SendReply("E01");
}
@@ -619,16 +727,16 @@ static void ReadRegisters() {
u8* bufptr = buffer;
for (int reg = 0; reg <= SP_REGISTER; reg++) {
- LongToGdbHex(bufptr + reg * 16, Core::CPU().GetReg(reg));
+ LongToGdbHex(bufptr + reg * 16, RegRead(reg, current_thread));
}
bufptr += (32 * 16);
- LongToGdbHex(bufptr, Core::CPU().GetPC());
+ LongToGdbHex(bufptr, RegRead(PC_REGISTER, current_thread));
bufptr += 16;
- IntToGdbHex(bufptr, Core::CPU().GetCPSR());
+ IntToGdbHex(bufptr, (u32)RegRead(CPSR_REGISTER, current_thread));
bufptr += 8;
@@ -647,11 +755,11 @@ static void WriteRegister() {
}
if (id <= SP_REGISTER) {
- Core::CPU().SetReg(id, GdbHexToLong(buffer_ptr));
+ RegWrite(id, GdbHexToLong(buffer_ptr), current_thread);
} else if (id == PC_REGISTER) {
- Core::CPU().SetPC(GdbHexToLong(buffer_ptr));
+ RegWrite(id, GdbHexToLong(buffer_ptr), current_thread);
} else if (id == CPSR_REGISTER) {
- Core::CPU().SetCPSR(GdbHexToInt(buffer_ptr));
+ RegWrite(id, GdbHexToInt(buffer_ptr), current_thread);
} else {
return SendReply("E01");
}
@@ -668,11 +776,11 @@ static void WriteRegisters() {
for (int i = 0, reg = 0; reg <= CPSR_REGISTER; i++, reg++) {
if (reg <= SP_REGISTER) {
- Core::CPU().SetReg(reg, GdbHexToLong(buffer_ptr + i * 16));
+ RegWrite(reg, GdbHexToLong(buffer_ptr + i * 16), current_thread);
} else if (reg == PC_REGISTER) {
- Core::CPU().SetPC(GdbHexToLong(buffer_ptr + i * 16));
+ RegWrite(PC_REGISTER, GdbHexToLong(buffer_ptr + i * 16), current_thread);
} else if (reg == CPSR_REGISTER) {
- Core::CPU().SetCPSR(GdbHexToInt(buffer_ptr + i * 16));
+ RegWrite(CPSR_REGISTER, GdbHexToInt(buffer_ptr + i * 16), current_thread);
} else {
UNIMPLEMENTED();
}
@@ -693,7 +801,7 @@ static void ReadMemory() {
u64 len =
HexToLong(start_offset, static_cast<u64>((command_buffer + command_length) - start_offset));
- LOG_DEBUG(Debug_GDBStub, "gdb: addr: %016llx len: %016llx\n", addr, len);
+ NGLOG_DEBUG(Debug_GDBStub, "gdb: addr: {:016X} len: {:016X}", addr, len);
if (len * 2 > sizeof(reply)) {
SendReply("E01");
@@ -735,7 +843,7 @@ static void WriteMemory() {
void Break(bool is_memory_break) {
if (!halt_loop) {
halt_loop = true;
- SendSignal(SIGTRAP);
+ send_trap = true;
}
memory_break = is_memory_break;
@@ -745,10 +853,10 @@ void Break(bool is_memory_break) {
static void Step() {
step_loop = true;
halt_loop = true;
- step_break = true;
- SendSignal(SIGTRAP);
+ send_trap = true;
}
+/// Tell the CPU if we hit a memory breakpoint.
bool IsMemoryBreak() {
if (IsConnected()) {
return false;
@@ -760,7 +868,6 @@ bool IsMemoryBreak() {
/// Tell the CPU to continue executing.
static void Continue() {
memory_break = false;
- step_break = false;
step_loop = false;
halt_loop = false;
}
@@ -781,8 +888,8 @@ static bool CommitBreakpoint(BreakpointType type, PAddr addr, u64 len) {
breakpoint.len = len;
p.insert({addr, breakpoint});
- LOG_DEBUG(Debug_GDBStub, "gdb: added %d breakpoint: %016" PRIx64 " bytes at %016" PRIx64 "\n",
- static_cast<int>(type), breakpoint.len, breakpoint.addr);
+ NGLOG_DEBUG(Debug_GDBStub, "gdb: added {} breakpoint: {:016X} bytes at {:016X}",
+ static_cast<int>(type), breakpoint.len, breakpoint.addr);
return true;
}
@@ -889,7 +996,7 @@ void HandlePacket() {
return;
}
- LOG_DEBUG(Debug_GDBStub, "Packet: %s", command_buffer);
+ NGLOG_DEBUG(Debug_GDBStub, "Packet: {}", command_buffer);
switch (command_buffer[0]) {
case 'q':
@@ -899,11 +1006,11 @@ void HandlePacket() {
HandleSetThread();
break;
case '?':
- SendSignal(latest_signal);
+ SendSignal(current_thread, latest_signal);
break;
case 'k':
Shutdown();
- LOG_INFO(Debug_GDBStub, "killed by gdb");
+ NGLOG_INFO(Debug_GDBStub, "killed by gdb");
return;
case 'g':
ReadRegisters();
@@ -936,6 +1043,9 @@ void HandlePacket() {
case 'Z':
AddBreakpoint();
break;
+ case 'T':
+ HandleThreadAlive();
+ break;
default:
SendReply("");
break;
@@ -982,7 +1092,7 @@ static void Init(u16 port) {
breakpoints_write.clear();
// Start gdb server
- LOG_INFO(Debug_GDBStub, "Starting GDB server on port %d...", port);
+ NGLOG_INFO(Debug_GDBStub, "Starting GDB server on port {}...", port);
sockaddr_in saddr_server = {};
saddr_server.sin_family = AF_INET;
@@ -995,28 +1105,28 @@ static void Init(u16 port) {
int tmpsock = static_cast<int>(socket(PF_INET, SOCK_STREAM, 0));
if (tmpsock == -1) {
- LOG_ERROR(Debug_GDBStub, "Failed to create gdb socket");
+ NGLOG_ERROR(Debug_GDBStub, "Failed to create gdb socket");
}
// Set socket to SO_REUSEADDR so it can always bind on the same port
int reuse_enabled = 1;
if (setsockopt(tmpsock, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse_enabled,
sizeof(reuse_enabled)) < 0) {
- LOG_ERROR(Debug_GDBStub, "Failed to set gdb socket option");
+ NGLOG_ERROR(Debug_GDBStub, "Failed to set gdb socket option");
}
const sockaddr* server_addr = reinterpret_cast<const sockaddr*>(&saddr_server);
socklen_t server_addrlen = sizeof(saddr_server);
if (bind(tmpsock, server_addr, server_addrlen) < 0) {
- LOG_ERROR(Debug_GDBStub, "Failed to bind gdb socket");
+ NGLOG_ERROR(Debug_GDBStub, "Failed to bind gdb socket");
}
if (listen(tmpsock, 1) < 0) {
- LOG_ERROR(Debug_GDBStub, "Failed to listen to gdb socket");
+ NGLOG_ERROR(Debug_GDBStub, "Failed to listen to gdb socket");
}
// Wait for gdb to connect
- LOG_INFO(Debug_GDBStub, "Waiting for gdb to connect...\n");
+ NGLOG_INFO(Debug_GDBStub, "Waiting for gdb to connect...");
sockaddr_in saddr_client;
sockaddr* client_addr = reinterpret_cast<sockaddr*>(&saddr_client);
socklen_t client_addrlen = sizeof(saddr_client);
@@ -1027,9 +1137,9 @@ static void Init(u16 port) {
halt_loop = false;
step_loop = false;
- LOG_ERROR(Debug_GDBStub, "Failed to accept gdb client");
+ NGLOG_ERROR(Debug_GDBStub, "Failed to accept gdb client");
} else {
- LOG_INFO(Debug_GDBStub, "Client connected.\n");
+ NGLOG_INFO(Debug_GDBStub, "Client connected.");
saddr_client.sin_addr.s_addr = ntohl(saddr_client.sin_addr.s_addr);
}
@@ -1048,7 +1158,7 @@ void Shutdown() {
return;
}
- LOG_INFO(Debug_GDBStub, "Stopping GDB ...");
+ NGLOG_INFO(Debug_GDBStub, "Stopping GDB ...");
if (gdbserver_socket != -1) {
shutdown(gdbserver_socket, SHUT_RDWR);
gdbserver_socket = -1;
@@ -1058,7 +1168,7 @@ void Shutdown() {
WSACleanup();
#endif
- LOG_INFO(Debug_GDBStub, "GDB stopped.");
+ NGLOG_INFO(Debug_GDBStub, "GDB stopped.");
}
bool IsServerEnabled() {
@@ -1080,4 +1190,11 @@ bool GetCpuStepFlag() {
void SetCpuStepFlag(bool is_step) {
step_loop = is_step;
}
+
+void SendTrap(Kernel::Thread* thread, int trap) {
+ if (send_trap) {
+ send_trap = false;
+ SendSignal(thread, trap);
+ }
+}
}; // namespace GDBStub
diff --git a/src/core/gdbstub/gdbstub.h b/src/core/gdbstub/gdbstub.h
index 201fca095..f2418c9e4 100644
--- a/src/core/gdbstub/gdbstub.h
+++ b/src/core/gdbstub/gdbstub.h
@@ -7,6 +7,7 @@
#pragma once
#include "common/common_types.h"
+#include "core/hle/kernel/thread.h"
namespace GDBStub {
@@ -91,4 +92,12 @@ bool GetCpuStepFlag();
* @param is_step
*/
void SetCpuStepFlag(bool is_step);
+
+/**
+ * Send trap signal from thread back to the gdbstub server.
+ *
+ * @param thread Sending thread.
+ * @param trap Trap no.
+ */
+void SendTrap(Kernel::Thread* thread, int trap);
} // namespace GDBStub
diff --git a/src/core/hle/ipc.h b/src/core/hle/ipc.h
index a6602e12c..eaa5395ac 100644
--- a/src/core/hle/ipc.h
+++ b/src/core/hle/ipc.h
@@ -29,9 +29,14 @@ enum class ControlCommand : u32 {
};
enum class CommandType : u32 {
+ Invalid = 0,
+ LegacyRequest = 1,
Close = 2,
+ LegacyControl = 3,
Request = 4,
Control = 5,
+ RequestWithContext = 6,
+ ControlWithContext = 7,
Unspecified,
};
@@ -167,6 +172,7 @@ struct DomainMessageHeader {
struct {
union {
BitField<0, 8, CommandType> command;
+ BitField<8, 8, u32_le> input_object_count;
BitField<16, 16, u32_le> size;
};
u32_le object_id;
diff --git a/src/core/hle/ipc_helpers.h b/src/core/hle/ipc_helpers.h
index 3f87c4297..24605a273 100644
--- a/src/core/hle/ipc_helpers.h
+++ b/src/core/hle/ipc_helpers.h
@@ -298,6 +298,13 @@ public:
template <typename T>
Kernel::SharedPtr<T> GetCopyObject(size_t index);
+
+ template <class T>
+ std::shared_ptr<T> PopIpcInterface() {
+ ASSERT(context->Session()->IsDomain());
+ ASSERT(context->GetDomainMessageHeader()->input_object_count > 0);
+ return context->GetDomainRequestHandler<T>(Pop<u32>() - 1);
+ }
};
/// Pop ///
diff --git a/src/core/hle/kernel/condition_variable.cpp b/src/core/hle/kernel/condition_variable.cpp
deleted file mode 100644
index a786d7f74..000000000
--- a/src/core/hle/kernel/condition_variable.cpp
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include "common/assert.h"
-#include "core/hle/kernel/condition_variable.h"
-#include "core/hle/kernel/errors.h"
-#include "core/hle/kernel/kernel.h"
-#include "core/hle/kernel/object_address_table.h"
-#include "core/hle/kernel/thread.h"
-
-namespace Kernel {
-
-ConditionVariable::ConditionVariable() {}
-ConditionVariable::~ConditionVariable() {}
-
-ResultVal<SharedPtr<ConditionVariable>> ConditionVariable::Create(VAddr guest_addr,
- std::string name) {
- SharedPtr<ConditionVariable> condition_variable(new ConditionVariable);
-
- condition_variable->name = std::move(name);
- condition_variable->guest_addr = guest_addr;
- condition_variable->mutex_addr = 0;
-
- // Condition variables are referenced by guest address, so track this in the kernel
- g_object_address_table.Insert(guest_addr, condition_variable);
-
- return MakeResult<SharedPtr<ConditionVariable>>(std::move(condition_variable));
-}
-
-bool ConditionVariable::ShouldWait(Thread* thread) const {
- return GetAvailableCount() <= 0;
-}
-
-void ConditionVariable::Acquire(Thread* thread) {
- if (GetAvailableCount() <= 0)
- return;
-
- SetAvailableCount(GetAvailableCount() - 1);
-}
-
-ResultCode ConditionVariable::Release(s32 target) {
- if (target == -1) {
- // When -1, wake up all waiting threads
- SetAvailableCount(static_cast<s32>(GetWaitingThreads().size()));
- WakeupAllWaitingThreads();
- } else {
- // Otherwise, wake up just a single thread
- SetAvailableCount(target);
- WakeupWaitingThread(GetHighestPriorityReadyThread());
- }
-
- return RESULT_SUCCESS;
-}
-
-s32 ConditionVariable::GetAvailableCount() const {
- return Memory::Read32(guest_addr);
-}
-
-void ConditionVariable::SetAvailableCount(s32 value) const {
- Memory::Write32(guest_addr, value);
-}
-
-} // namespace Kernel
diff --git a/src/core/hle/kernel/condition_variable.h b/src/core/hle/kernel/condition_variable.h
deleted file mode 100644
index 1c9f06769..000000000
--- a/src/core/hle/kernel/condition_variable.h
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <string>
-#include <queue>
-#include "common/common_types.h"
-#include "core/hle/kernel/kernel.h"
-#include "core/hle/kernel/wait_object.h"
-#include "core/hle/result.h"
-
-namespace Kernel {
-
-class ConditionVariable final : public WaitObject {
-public:
- /**
- * Creates a condition variable.
- * @param guest_addr Address of the object tracking the condition variable in guest memory. If
- * specified, this condition variable will update the guest object when its state changes.
- * @param name Optional name of condition variable.
- * @return The created condition variable.
- */
- static ResultVal<SharedPtr<ConditionVariable>> Create(VAddr guest_addr,
- std::string name = "Unknown");
-
- std::string GetTypeName() const override {
- return "ConditionVariable";
- }
- std::string GetName() const override {
- return name;
- }
-
- static const HandleType HANDLE_TYPE = HandleType::ConditionVariable;
- HandleType GetHandleType() const override {
- return HANDLE_TYPE;
- }
-
- s32 GetAvailableCount() const;
- void SetAvailableCount(s32 value) const;
-
- std::string name; ///< Name of condition variable (optional)
- VAddr guest_addr; ///< Address of the guest condition variable value
- VAddr mutex_addr; ///< (optional) Address of guest mutex value associated with this condition
- ///< variable, used for implementing events
-
- bool ShouldWait(Thread* thread) const override;
- void Acquire(Thread* thread) override;
-
- /**
- * Releases a slot from a condition variable.
- * @param target The number of threads to wakeup, -1 is all.
- * @return ResultCode indicating if the operation succeeded.
- */
- ResultCode Release(s32 target);
-
-private:
- ConditionVariable();
- ~ConditionVariable() override;
-};
-
-} // namespace Kernel
diff --git a/src/core/hle/kernel/errors.h b/src/core/hle/kernel/errors.h
index 29d8dfdaa..e1b5430bf 100644
--- a/src/core/hle/kernel/errors.h
+++ b/src/core/hle/kernel/errors.h
@@ -20,7 +20,10 @@ enum {
MaxConnectionsReached = 52,
// Confirmed Switch OS error codes
+ MisalignedAddress = 102,
+ InvalidProcessorId = 113,
InvalidHandle = 114,
+ InvalidCombination = 116,
Timeout = 117,
SynchronizationCanceled = 118,
TooLarge = 119,
diff --git a/src/core/hle/kernel/handle_table.cpp b/src/core/hle/kernel/handle_table.cpp
index 3beb55753..f7a9920d8 100644
--- a/src/core/hle/kernel/handle_table.cpp
+++ b/src/core/hle/kernel/handle_table.cpp
@@ -5,6 +5,7 @@
#include <utility>
#include "common/assert.h"
#include "common/logging/log.h"
+#include "core/core.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/kernel.h"
@@ -25,7 +26,7 @@ ResultVal<Handle> HandleTable::Create(SharedPtr<Object> obj) {
u16 slot = next_free_slot;
if (slot >= generations.size()) {
- LOG_ERROR(Kernel, "Unable to allocate Handle, too many slots in use.");
+ NGLOG_ERROR(Kernel, "Unable to allocate Handle, too many slots in use.");
return ERR_OUT_OF_HANDLES;
}
next_free_slot = generations[slot];
@@ -47,7 +48,7 @@ ResultVal<Handle> HandleTable::Create(SharedPtr<Object> obj) {
ResultVal<Handle> HandleTable::Duplicate(Handle handle) {
SharedPtr<Object> object = GetGeneric(handle);
if (object == nullptr) {
- LOG_ERROR(Kernel, "Tried to duplicate invalid handle: %08X", handle);
+ NGLOG_ERROR(Kernel, "Tried to duplicate invalid handle: {:08X}", handle);
return ERR_INVALID_HANDLE;
}
return Create(std::move(object));
@@ -77,7 +78,7 @@ SharedPtr<Object> HandleTable::GetGeneric(Handle handle) const {
if (handle == CurrentThread) {
return GetCurrentThread();
} else if (handle == CurrentProcess) {
- return g_current_process;
+ return Core::CurrentProcess();
}
if (!IsValid(handle)) {
diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp
index d9faf4b53..01904467e 100644
--- a/src/core/hle/kernel/hle_ipc.cpp
+++ b/src/core/hle/kernel/hle_ipc.cpp
@@ -7,6 +7,7 @@
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/event.h"
#include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/kernel/kernel.h"
@@ -26,6 +27,32 @@ void SessionRequestHandler::ClientDisconnected(SharedPtr<ServerSession> server_s
boost::range::remove_erase(connected_sessions, server_session);
}
+SharedPtr<Event> HLERequestContext::SleepClientThread(SharedPtr<Thread> thread,
+ const std::string& reason, u64 timeout,
+ WakeupCallback&& callback) {
+
+ // Put the client thread to sleep until the wait event is signaled or the timeout expires.
+ thread->wakeup_callback =
+ [context = *this, callback](ThreadWakeupReason reason, SharedPtr<Thread> thread,
+ SharedPtr<WaitObject> object, size_t index) mutable -> bool {
+ ASSERT(thread->status == THREADSTATUS_WAIT_HLE_EVENT);
+ callback(thread, context, reason);
+ context.WriteToOutgoingCommandBuffer(*thread);
+ return true;
+ };
+
+ auto event = Kernel::Event::Create(Kernel::ResetType::OneShot, "HLE Pause Event: " + reason);
+ thread->status = THREADSTATUS_WAIT_HLE_EVENT;
+ thread->wait_objects = {event};
+ event->AddWaitingThread(thread);
+
+ if (timeout > 0) {
+ thread->WakeAfterDelay(timeout);
+ }
+
+ return event;
+}
+
HLERequestContext::HLERequestContext(SharedPtr<Kernel::ServerSession> server_session)
: server_session(std::move(server_session)) {
cmd_buf[0] = 0;
@@ -35,7 +62,7 @@ HLERequestContext::~HLERequestContext() = default;
void HLERequestContext::ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming) {
IPC::RequestParser rp(src_cmdbuf);
- command_header = std::make_unique<IPC::CommandHeader>(rp.PopRaw<IPC::CommandHeader>());
+ command_header = std::make_shared<IPC::CommandHeader>(rp.PopRaw<IPC::CommandHeader>());
if (command_header->type == IPC::CommandType::Close) {
// Close does not populate the rest of the IPC header
@@ -45,7 +72,7 @@ void HLERequestContext::ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming) {
// If handle descriptor is present, add size of it
if (command_header->enable_handle_descriptor) {
handle_descriptor_header =
- std::make_unique<IPC::HandleDescriptorHeader>(rp.PopRaw<IPC::HandleDescriptorHeader>());
+ std::make_shared<IPC::HandleDescriptorHeader>(rp.PopRaw<IPC::HandleDescriptorHeader>());
if (handle_descriptor_header->send_current_pid) {
rp.Skip(2, false);
}
@@ -83,20 +110,22 @@ void HLERequestContext::ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming) {
// Padding to align to 16 bytes
rp.AlignWithPadding();
- if (Session()->IsDomain() && (command_header->type == IPC::CommandType::Request || !incoming)) {
+ if (Session()->IsDomain() && ((command_header->type == IPC::CommandType::Request ||
+ command_header->type == IPC::CommandType::RequestWithContext) ||
+ !incoming)) {
// 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_unique<IPC::DomainMessageHeader>(rp.PopRaw<IPC::DomainMessageHeader>());
+ std::make_shared<IPC::DomainMessageHeader>(rp.PopRaw<IPC::DomainMessageHeader>());
} else {
if (Session()->IsDomain())
- LOG_WARNING(IPC, "Domain request has no DomainMessageHeader!");
+ NGLOG_WARNING(IPC, "Domain request has no DomainMessageHeader!");
}
}
data_payload_header =
- std::make_unique<IPC::DataPayloadHeader>(rp.PopRaw<IPC::DataPayloadHeader>());
+ std::make_shared<IPC::DataPayloadHeader>(rp.PopRaw<IPC::DataPayloadHeader>());
data_payload_offset = rp.GetCurrentOffset();
@@ -159,8 +188,11 @@ ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer(u32_le* src_cmdb
return RESULT_SUCCESS;
}
-ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf, Process& dst_process,
- HandleTable& dst_table) {
+ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(Thread& thread) {
+ std::array<u32, IPC::COMMAND_BUFFER_LENGTH> dst_cmdbuf;
+ Memory::ReadBlock(*thread.owner_process, thread.GetTLSAddress(), dst_cmdbuf.data(),
+ dst_cmdbuf.size() * sizeof(u32));
+
// The header was already built in the internal command buffer. Attempt to parse it to verify
// the integrity and then copy it over to the target command buffer.
ParseCommandBuffer(cmd_buf.data(), false);
@@ -171,7 +203,7 @@ ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf, P
if (domain_message_header)
size -= sizeof(IPC::DomainMessageHeader) / sizeof(u32);
- std::copy_n(cmd_buf.begin(), size, dst_cmdbuf);
+ std::copy_n(cmd_buf.begin(), size, dst_cmdbuf.data());
if (command_header->enable_handle_descriptor) {
ASSERT_MSG(!move_objects.empty() || !copy_objects.empty(),
@@ -213,50 +245,63 @@ ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf, P
dst_cmdbuf[domain_offset++] = static_cast<u32_le>(request_handlers.size());
}
}
+
+ // Copy the translated command buffer back into the thread's command buffer area.
+ Memory::WriteBlock(*thread.owner_process, thread.GetTLSAddress(), dst_cmdbuf.data(),
+ dst_cmdbuf.size() * sizeof(u32));
+
return RESULT_SUCCESS;
}
-std::vector<u8> HLERequestContext::ReadBuffer() const {
+std::vector<u8> HLERequestContext::ReadBuffer(int buffer_index) const {
std::vector<u8> buffer;
- const bool is_buffer_a{BufferDescriptorA().size() && BufferDescriptorA()[0].Size()};
+ const bool is_buffer_a{BufferDescriptorA().size() && BufferDescriptorA()[buffer_index].Size()};
if (is_buffer_a) {
- buffer.resize(BufferDescriptorA()[0].Size());
- Memory::ReadBlock(BufferDescriptorA()[0].Address(), buffer.data(), buffer.size());
+ buffer.resize(BufferDescriptorA()[buffer_index].Size());
+ Memory::ReadBlock(BufferDescriptorA()[buffer_index].Address(), buffer.data(),
+ buffer.size());
} else {
- buffer.resize(BufferDescriptorX()[0].Size());
- Memory::ReadBlock(BufferDescriptorX()[0].Address(), buffer.data(), buffer.size());
+ buffer.resize(BufferDescriptorX()[buffer_index].Size());
+ Memory::ReadBlock(BufferDescriptorX()[buffer_index].Address(), buffer.data(),
+ buffer.size());
}
return buffer;
}
-size_t HLERequestContext::WriteBuffer(const void* buffer, size_t size) const {
- const bool is_buffer_b{BufferDescriptorB().size() && BufferDescriptorB()[0].Size()};
-
- ASSERT_MSG(size <= GetWriteBufferSize(), "Size %d is too big", size);
+size_t HLERequestContext::WriteBuffer(const void* buffer, size_t size, int buffer_index) const {
+ const bool is_buffer_b{BufferDescriptorB().size() && BufferDescriptorB()[buffer_index].Size()};
+ const size_t buffer_size{GetWriteBufferSize(buffer_index)};
+ if (size > buffer_size) {
+ NGLOG_CRITICAL(Core, "size ({:016X}) is greater than buffer_size ({:016X})", size,
+ buffer_size);
+ size = buffer_size; // TODO(bunnei): This needs to be HW tested
+ }
if (is_buffer_b) {
- Memory::WriteBlock(BufferDescriptorB()[0].Address(), buffer, size);
+ Memory::WriteBlock(BufferDescriptorB()[buffer_index].Address(), buffer, size);
} else {
- Memory::WriteBlock(BufferDescriptorC()[0].Address(), buffer, size);
+ Memory::WriteBlock(BufferDescriptorC()[buffer_index].Address(), buffer, size);
}
return size;
}
-size_t HLERequestContext::WriteBuffer(const std::vector<u8>& buffer) const {
+size_t HLERequestContext::WriteBuffer(const std::vector<u8>& buffer, int buffer_index) const {
return WriteBuffer(buffer.data(), buffer.size());
}
-size_t HLERequestContext::GetReadBufferSize() const {
- const bool is_buffer_a{BufferDescriptorA().size() && BufferDescriptorA()[0].Size()};
- return is_buffer_a ? BufferDescriptorA()[0].Size() : BufferDescriptorX()[0].Size();
+size_t HLERequestContext::GetReadBufferSize(int buffer_index) const {
+ const bool is_buffer_a{BufferDescriptorA().size() && BufferDescriptorA()[buffer_index].Size()};
+ return is_buffer_a ? BufferDescriptorA()[buffer_index].Size()
+ : BufferDescriptorX()[buffer_index].Size();
}
-size_t HLERequestContext::GetWriteBufferSize() const {
- const bool is_buffer_b{BufferDescriptorB().size() && BufferDescriptorB()[0].Size()};
- return is_buffer_b ? BufferDescriptorB()[0].Size() : BufferDescriptorC()[0].Size();
+size_t HLERequestContext::GetWriteBufferSize(int buffer_index) const {
+ const bool is_buffer_b{BufferDescriptorB().size() && BufferDescriptorB()[buffer_index].Size()};
+ return is_buffer_b ? BufferDescriptorB()[buffer_index].Size()
+ : BufferDescriptorC()[buffer_index].Size();
}
std::string HLERequestContext::Description() const {
diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h
index b5631b773..376263eac 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 <string>
#include <vector>
#include <boost/container/small_vector.hpp>
#include "common/common_types.h"
@@ -13,6 +14,7 @@
#include "core/hle/ipc.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/server_session.h"
+#include "core/hle/kernel/thread.h"
namespace Service {
class ServiceFrameworkBase;
@@ -24,6 +26,7 @@ class Domain;
class HandleTable;
class HLERequestContext;
class Process;
+class Event;
/**
* Interface implemented by HLE Session handlers.
@@ -102,14 +105,31 @@ public:
return server_session;
}
+ using WakeupCallback = std::function<void(SharedPtr<Thread> thread, HLERequestContext& context,
+ ThreadWakeupReason reason)>;
+
+ /**
+ * Puts the specified guest thread to sleep until the returned event is signaled or until the
+ * specified timeout expires.
+ * @param 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.
+ * @param callback Callback to be invoked when the thread is resumed. This callback must write
+ * the entire command response once again, regardless of the state of it before this function
+ * was called.
+ * @returns Event that when signaled will resume the thread and call the callback function.
+ */
+ SharedPtr<Event> SleepClientThread(SharedPtr<Thread> thread, const std::string& reason,
+ u64 timeout, WakeupCallback&& callback);
+
void ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming);
/// Populates this context with data from the requesting process/thread.
ResultCode PopulateFromIncomingCommandBuffer(u32_le* src_cmdbuf, Process& src_process,
HandleTable& src_table);
/// Writes data from this context back to the requesting process/thread.
- ResultCode WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf, Process& dst_process,
- HandleTable& dst_table);
+ ResultCode WriteToOutgoingCommandBuffer(Thread& thread);
u32_le GetCommand() const {
return command;
@@ -139,24 +159,24 @@ public:
return buffer_c_desciptors;
}
- const std::unique_ptr<IPC::DomainMessageHeader>& GetDomainMessageHeader() const {
+ const std::shared_ptr<IPC::DomainMessageHeader>& GetDomainMessageHeader() const {
return domain_message_header;
}
/// Helper function to read a buffer using the appropriate buffer descriptor
- std::vector<u8> ReadBuffer() const;
+ std::vector<u8> ReadBuffer(int buffer_index = 0) const;
/// Helper function to write a buffer using the appropriate buffer descriptor
- size_t WriteBuffer(const void* buffer, size_t size) const;
+ size_t WriteBuffer(const void* buffer, size_t size, int buffer_index = 0) const;
/// Helper function to write a buffer using the appropriate buffer descriptor
- size_t WriteBuffer(const std::vector<u8>& buffer) const;
+ size_t WriteBuffer(const std::vector<u8>& buffer, int buffer_index = 0) const;
/// Helper function to get the size of the input buffer
- size_t GetReadBufferSize() const;
+ size_t GetReadBufferSize(int buffer_index = 0) const;
/// Helper function to get the size of the output buffer
- size_t GetWriteBufferSize() const;
+ size_t GetWriteBufferSize(int buffer_index = 0) const;
template <typename T>
SharedPtr<T> GetCopyObject(size_t index) {
@@ -182,6 +202,16 @@ public:
domain_objects.emplace_back(std::move(object));
}
+ template <typename T>
+ std::shared_ptr<T> GetDomainRequestHandler(size_t index) const {
+ return std::static_pointer_cast<T>(domain_request_handlers[index]);
+ }
+
+ void SetDomainRequestHandlers(
+ const std::vector<std::shared_ptr<SessionRequestHandler>>& handlers) {
+ domain_request_handlers = handlers;
+ }
+
/// Clears the list of objects so that no lingering objects are written accidentally to the
/// response buffer.
void ClearIncomingObjects() {
@@ -212,10 +242,10 @@ private:
boost::container::small_vector<SharedPtr<Object>, 8> copy_objects;
boost::container::small_vector<std::shared_ptr<SessionRequestHandler>, 8> domain_objects;
- std::unique_ptr<IPC::CommandHeader> command_header;
- std::unique_ptr<IPC::HandleDescriptorHeader> handle_descriptor_header;
- std::unique_ptr<IPC::DataPayloadHeader> data_payload_header;
- std::unique_ptr<IPC::DomainMessageHeader> domain_message_header;
+ 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::vector<IPC::BufferDescriptorX> buffer_x_desciptors;
std::vector<IPC::BufferDescriptorABW> buffer_a_desciptors;
std::vector<IPC::BufferDescriptorABW> buffer_b_desciptors;
@@ -225,6 +255,8 @@ private:
unsigned data_payload_offset{};
unsigned buffer_c_offset{};
u32_le command{};
+
+ std::vector<std::shared_ptr<SessionRequestHandler>> domain_request_handlers;
};
} // namespace Kernel
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp
index b0c3f4ae1..b325b879b 100644
--- a/src/core/hle/kernel/kernel.cpp
+++ b/src/core/hle/kernel/kernel.cpp
@@ -41,7 +41,6 @@ void Shutdown() {
g_object_address_table.Clear();
Kernel::ThreadingShutdown();
- g_current_process = nullptr;
Kernel::TimersShutdown();
Kernel::ResourceLimitsShutdown();
diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h
index c77e58f3c..402ae900f 100644
--- a/src/core/hle/kernel/kernel.h
+++ b/src/core/hle/kernel/kernel.h
@@ -18,12 +18,10 @@ using Handle = u32;
enum class HandleType : u32 {
Unknown,
Event,
- Mutex,
SharedMemory,
Thread,
Process,
AddressArbiter,
- ConditionVariable,
Timer,
ResourceLimit,
CodeSet,
@@ -33,10 +31,6 @@ enum class HandleType : u32 {
ServerSession,
};
-enum {
- DEFAULT_STACK_SIZE = 0x10000,
-};
-
enum class ResetType {
OneShot,
Sticky,
@@ -67,9 +61,7 @@ public:
bool IsWaitable() const {
switch (GetHandleType()) {
case HandleType::Event:
- case HandleType::Mutex:
case HandleType::Thread:
- case HandleType::ConditionVariable:
case HandleType::Timer:
case HandleType::ServerPort:
case HandleType::ServerSession:
diff --git a/src/core/hle/kernel/mutex.cpp b/src/core/hle/kernel/mutex.cpp
index 0b9dc700c..bc144f3de 100644
--- a/src/core/hle/kernel/mutex.cpp
+++ b/src/core/hle/kernel/mutex.cpp
@@ -7,6 +7,7 @@
#include <boost/range/algorithm_ext/erase.hpp>
#include "common/assert.h"
#include "core/core.h"
+#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/mutex.h"
@@ -15,124 +16,119 @@
namespace Kernel {
-void ReleaseThreadMutexes(Thread* thread) {
- for (auto& mtx : thread->held_mutexes) {
- mtx->SetHasWaiters(false);
- mtx->SetHoldingThread(nullptr);
- mtx->WakeupAllWaitingThreads();
- }
- thread->held_mutexes.clear();
-}
+/// Returns the number of threads that are waiting for a mutex, and the highest priority one among
+/// those.
+static std::pair<SharedPtr<Thread>, u32> GetHighestPriorityMutexWaitingThread(
+ SharedPtr<Thread> current_thread, VAddr mutex_addr) {
-Mutex::Mutex() {}
-Mutex::~Mutex() {}
+ SharedPtr<Thread> highest_priority_thread;
+ u32 num_waiters = 0;
-SharedPtr<Mutex> Mutex::Create(SharedPtr<Kernel::Thread> holding_thread, VAddr guest_addr,
- std::string name) {
- SharedPtr<Mutex> mutex(new Mutex);
+ for (auto& thread : current_thread->wait_mutex_threads) {
+ if (thread->mutex_wait_address != mutex_addr)
+ continue;
- mutex->guest_addr = guest_addr;
- mutex->name = std::move(name);
+ ASSERT(thread->status == THREADSTATUS_WAIT_MUTEX);
- // If mutex was initialized with a holding thread, acquire it by the holding thread
- if (holding_thread) {
- mutex->Acquire(holding_thread.get());
+ ++num_waiters;
+ if (highest_priority_thread == nullptr ||
+ thread->GetPriority() < highest_priority_thread->GetPriority()) {
+ highest_priority_thread = thread;
+ }
}
- // Mutexes are referenced by guest address, so track this in the kernel
- g_object_address_table.Insert(guest_addr, mutex);
-
- return mutex;
+ return {highest_priority_thread, num_waiters};
}
-bool Mutex::ShouldWait(Thread* thread) const {
- auto holding_thread = GetHoldingThread();
- return holding_thread != nullptr && thread != holding_thread;
+/// Update the mutex owner field of all threads waiting on the mutex to point to the new owner.
+static void TransferMutexOwnership(VAddr mutex_addr, SharedPtr<Thread> current_thread,
+ SharedPtr<Thread> new_owner) {
+ auto threads = current_thread->wait_mutex_threads;
+ for (auto& thread : threads) {
+ if (thread->mutex_wait_address != mutex_addr)
+ continue;
+
+ ASSERT(thread->lock_owner == current_thread);
+ current_thread->RemoveMutexWaiter(thread);
+ if (new_owner != thread)
+ new_owner->AddMutexWaiter(thread);
+ }
}
-void Mutex::Acquire(Thread* thread) {
- ASSERT_MSG(!ShouldWait(thread), "object unavailable!");
+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 ResultCode(ErrorModule::Kernel, ErrCodes::MisalignedAddress);
+ }
- priority = thread->current_priority;
- thread->held_mutexes.insert(this);
- SetHoldingThread(thread);
- thread->UpdatePriority();
- Core::System::GetInstance().PrepareReschedule();
-}
+ SharedPtr<Thread> holding_thread = g_handle_table.Get<Thread>(holding_thread_handle);
+ SharedPtr<Thread> requesting_thread = g_handle_table.Get<Thread>(requesting_thread_handle);
-ResultCode Mutex::Release(Thread* thread) {
- auto holding_thread = GetHoldingThread();
- ASSERT(holding_thread);
+ // TODO(Subv): It is currently unknown if it is possible to lock a mutex in behalf of another
+ // thread.
+ ASSERT(requesting_thread == GetCurrentThread());
- // We can only release the mutex if it's held by the calling thread.
- ASSERT(thread == holding_thread);
+ 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)
+ return ERR_INVALID_HANDLE;
+
+ // Wait until the mutex is released
+ GetCurrentThread()->mutex_wait_address = address;
+ GetCurrentThread()->wait_handle = requesting_thread_handle;
+
+ GetCurrentThread()->status = THREADSTATUS_WAIT_MUTEX;
+ GetCurrentThread()->wakeup_callback = nullptr;
+
+ // Update the lock holder thread's priority to prevent priority inversion.
+ holding_thread->AddMutexWaiter(GetCurrentThread());
- holding_thread->held_mutexes.erase(this);
- holding_thread->UpdatePriority();
- SetHoldingThread(nullptr);
- SetHasWaiters(!GetWaitingThreads().empty());
- WakeupAllWaitingThreads();
Core::System::GetInstance().PrepareReschedule();
return RESULT_SUCCESS;
}
-void Mutex::AddWaitingThread(SharedPtr<Thread> thread) {
- WaitObject::AddWaitingThread(thread);
- thread->pending_mutexes.insert(this);
- SetHasWaiters(true);
- UpdatePriority();
-}
-
-void Mutex::RemoveWaitingThread(Thread* thread) {
- WaitObject::RemoveWaitingThread(thread);
- thread->pending_mutexes.erase(this);
- if (!GetHasWaiters())
- SetHasWaiters(!GetWaitingThreads().empty());
- UpdatePriority();
-}
+ResultCode Mutex::Release(VAddr address) {
+ // The mutex address must be 4-byte aligned
+ if ((address % sizeof(u32)) != 0) {
+ return ResultCode(ErrorModule::Kernel, ErrCodes::MisalignedAddress);
+ }
-void Mutex::UpdatePriority() {
- if (!GetHoldingThread())
- return;
+ auto [thread, num_waiters] = GetHighestPriorityMutexWaitingThread(GetCurrentThread(), address);
- u32 best_priority = THREADPRIO_LOWEST;
- for (auto& waiter : GetWaitingThreads()) {
- if (waiter->current_priority < best_priority)
- best_priority = waiter->current_priority;
+ // There are no more threads waiting for the mutex, release it completely.
+ if (thread == nullptr) {
+ Memory::Write32(address, 0);
+ return RESULT_SUCCESS;
}
- if (best_priority != priority) {
- priority = best_priority;
- GetHoldingThread()->UpdatePriority();
- }
-}
+ // Transfer the ownership of the mutex from the previous owner to the new one.
+ TransferMutexOwnership(address, GetCurrentThread(), thread);
-Handle Mutex::GetOwnerHandle() const {
- GuestState guest_state{Memory::Read32(guest_addr)};
- return guest_state.holding_thread_handle;
-}
+ u32 mutex_value = thread->wait_handle;
-SharedPtr<Thread> Mutex::GetHoldingThread() const {
- GuestState guest_state{Memory::Read32(guest_addr)};
- return g_handle_table.Get<Thread>(guest_state.holding_thread_handle);
-}
+ if (num_waiters >= 2) {
+ // Notify the guest that there are still some threads waiting for the mutex
+ mutex_value |= Mutex::MutexHasWaitersFlag;
+ }
-void Mutex::SetHoldingThread(SharedPtr<Thread> thread) {
- GuestState guest_state{Memory::Read32(guest_addr)};
- guest_state.holding_thread_handle.Assign(thread ? thread->guest_handle : 0);
- Memory::Write32(guest_addr, guest_state.raw);
-}
+ // Grant the mutex to the next waiting thread and resume it.
+ Memory::Write32(address, mutex_value);
-bool Mutex::GetHasWaiters() const {
- GuestState guest_state{Memory::Read32(guest_addr)};
- return guest_state.has_waiters != 0;
-}
+ ASSERT(thread->status == THREADSTATUS_WAIT_MUTEX);
+ thread->ResumeFromWait();
-void Mutex::SetHasWaiters(bool has_waiters) {
- GuestState guest_state{Memory::Read32(guest_addr)};
- guest_state.has_waiters.Assign(has_waiters ? 1 : 0);
- Memory::Write32(guest_addr, guest_state.raw);
-}
+ thread->lock_owner = nullptr;
+ thread->condvar_wait_address = 0;
+ thread->mutex_wait_address = 0;
+ thread->wait_handle = 0;
+ return RESULT_SUCCESS;
+}
} // namespace Kernel
diff --git a/src/core/hle/kernel/mutex.h b/src/core/hle/kernel/mutex.h
index 38db21005..3117e7c70 100644
--- a/src/core/hle/kernel/mutex.h
+++ b/src/core/hle/kernel/mutex.h
@@ -15,87 +15,23 @@ namespace Kernel {
class Thread;
-class Mutex final : public WaitObject {
+class Mutex final {
public:
- /**
- * Creates a mutex.
- * @param holding_thread Specifies a thread already holding the mutex. If not nullptr, this
- * thread will acquire the mutex.
- * @param guest_addr Address of the object tracking the mutex in guest memory. If specified,
- * this mutex will update the guest object when its state changes.
- * @param name Optional name of mutex
- * @return Pointer to new Mutex object
- */
- static SharedPtr<Mutex> Create(SharedPtr<Kernel::Thread> holding_thread, VAddr guest_addr = 0,
- std::string name = "Unknown");
+ /// 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;
- std::string GetTypeName() const override {
- return "Mutex";
- }
- std::string GetName() const override {
- return name;
- }
+ /// Attempts to acquire a mutex at the specified address.
+ static ResultCode TryAcquire(VAddr address, Handle holding_thread_handle,
+ Handle requesting_thread_handle);
- static const HandleType HANDLE_TYPE = HandleType::Mutex;
- HandleType GetHandleType() const override {
- return HANDLE_TYPE;
- }
-
- u32 priority; ///< The priority of the mutex, used for priority inheritance.
- std::string name; ///< Name of mutex (optional)
- VAddr guest_addr; ///< Address of the guest mutex value
-
- /**
- * Elevate the mutex priority to the best priority
- * among the priorities of all its waiting threads.
- */
- void UpdatePriority();
-
- bool ShouldWait(Thread* thread) const override;
- void Acquire(Thread* thread) override;
-
- void AddWaitingThread(SharedPtr<Thread> thread) override;
- void RemoveWaitingThread(Thread* thread) override;
-
- /**
- * Attempts to release the mutex from the specified thread.
- * @param thread Thread that wants to release the mutex.
- * @returns The result code of the operation.
- */
- ResultCode Release(Thread* thread);
-
- /// Gets the handle to the holding process stored in the guest state.
- Handle GetOwnerHandle() const;
-
- /// Gets the Thread pointed to by the owner handle
- SharedPtr<Thread> GetHoldingThread() const;
- /// Sets the holding process handle in the guest state.
- void SetHoldingThread(SharedPtr<Thread> thread);
-
- /// Returns the has_waiters bit in the guest state.
- bool GetHasWaiters() const;
- /// Sets the has_waiters bit in the guest state.
- void SetHasWaiters(bool has_waiters);
+ /// Releases the mutex at the specified address.
+ static ResultCode Release(VAddr address);
private:
- Mutex();
- ~Mutex() override;
-
- /// Object in guest memory used to track the mutex state
- union GuestState {
- u32_le raw;
- /// Handle of the thread that currently holds the mutex, 0 if available
- BitField<0, 30, u32_le> holding_thread_handle;
- /// 1 when there are threads waiting for this mutex, otherwise 0
- BitField<30, 1, u32_le> has_waiters;
- };
- static_assert(sizeof(GuestState) == 4, "GuestState size is incorrect");
+ Mutex() = default;
+ ~Mutex() = default;
};
-/**
- * Releases all the mutexes held by the specified thread
- * @param thread Thread that is holding the mutexes
- */
-void ReleaseThreadMutexes(Thread* thread);
-
} // namespace Kernel
diff --git a/src/core/hle/kernel/object_address_table.cpp b/src/core/hle/kernel/object_address_table.cpp
index 434c16add..ec97f6f8e 100644
--- a/src/core/hle/kernel/object_address_table.cpp
+++ b/src/core/hle/kernel/object_address_table.cpp
@@ -10,12 +10,12 @@ namespace Kernel {
ObjectAddressTable g_object_address_table;
void ObjectAddressTable::Insert(VAddr addr, SharedPtr<Object> obj) {
- ASSERT_MSG(objects.find(addr) == objects.end(), "Object already exists with addr=0x%llx", addr);
+ ASSERT_MSG(objects.find(addr) == objects.end(), "Object already exists with addr=0x{:X}", addr);
objects[addr] = obj;
}
void ObjectAddressTable::Close(VAddr addr) {
- ASSERT_MSG(objects.find(addr) != objects.end(), "Object does not exist with addr=0x%llx", addr);
+ ASSERT_MSG(objects.find(addr) != objects.end(), "Object does not exist with addr=0x{:X}", addr);
objects.erase(addr);
}
diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp
index 8e74059ea..651d932d3 100644
--- a/src/core/hle/kernel/process.cpp
+++ b/src/core/hle/kernel/process.cpp
@@ -20,12 +20,9 @@ namespace Kernel {
// Lists all processes that exist in the current session.
static std::vector<SharedPtr<Process>> process_list;
-SharedPtr<CodeSet> CodeSet::Create(std::string name, u64 program_id) {
+SharedPtr<CodeSet> CodeSet::Create(std::string name) {
SharedPtr<CodeSet> codeset(new CodeSet);
-
codeset->name = std::move(name);
- codeset->program_id = program_id;
-
return codeset;
}
@@ -41,6 +38,7 @@ SharedPtr<Process> Process::Create(std::string&& name) {
process->flags.raw = 0;
process->flags.memory_region.Assign(MemoryRegion::APPLICATION);
process->status = ProcessStatus::Created;
+ process->program_id = 0;
process_list.push_back(process);
return process;
@@ -56,7 +54,7 @@ void Process::ParseKernelCaps(const u32* kernel_caps, size_t len) {
continue;
} else if ((type & 0xF00) == 0xE00) { // 0x0FFF
// Allowed interrupts list
- LOG_WARNING(Loader, "ExHeader allowed interrupts list ignored");
+ NGLOG_WARNING(Loader, "ExHeader allowed interrupts list ignored");
} else if ((type & 0xF80) == 0xF00) { // 0x07FF
// Allowed syscalls mask
unsigned int index = ((descriptor >> 24) & 7) * 24;
@@ -76,7 +74,7 @@ void Process::ParseKernelCaps(const u32* kernel_caps, size_t len) {
} else if ((type & 0xFFE) == 0xFF8) { // 0x001F
// Mapped memory range
if (i + 1 >= len || ((kernel_caps[i + 1] >> 20) & 0xFFE) != 0xFF8) {
- LOG_WARNING(Loader, "Incomplete exheader memory range descriptor ignored.");
+ NGLOG_WARNING(Loader, "Incomplete exheader memory range descriptor ignored.");
continue;
}
u32 end_desc = kernel_caps[i + 1];
@@ -111,19 +109,21 @@ void Process::ParseKernelCaps(const u32* kernel_caps, size_t len) {
int minor = kernel_version & 0xFF;
int major = (kernel_version >> 8) & 0xFF;
- LOG_INFO(Loader, "ExHeader kernel version: %d.%d", major, minor);
+ NGLOG_INFO(Loader, "ExHeader kernel version: {}.{}", major, minor);
} else {
- LOG_ERROR(Loader, "Unhandled kernel caps descriptor: 0x%08X", descriptor);
+ NGLOG_ERROR(Loader, "Unhandled kernel caps descriptor: 0x{:08X}", descriptor);
}
}
}
void Process::Run(VAddr entry_point, s32 main_thread_priority, u32 stack_size) {
- // Allocate and map stack
+ // 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.
vm_manager
- .MapMemoryBlock(Memory::HEAP_VADDR_END - stack_size,
+ .MapMemoryBlock(Memory::STACK_AREA_VADDR_END - stack_size,
std::make_shared<std::vector<u8>>(stack_size, 0), 0, stack_size,
- MemoryState::Heap)
+ MemoryState::Mapped)
.Unwrap();
misc_memory_used += stack_size;
memory_region->used += stack_size;
@@ -134,7 +134,7 @@ void Process::Run(VAddr entry_point, s32 main_thread_priority, u32 stack_size) {
HandleSpecialMapping(vm_manager, mapping);
}
- vm_manager.LogLayout(Log::Level::Debug);
+ vm_manager.LogLayout();
status = ProcessStatus::Running;
Kernel::SetupMainThread(entry_point, main_thread_priority, this);
@@ -155,9 +155,9 @@ void Process::LoadModule(SharedPtr<CodeSet> module_, VAddr base_addr) {
};
// Map CodeSet segments
- MapSegment(module_->code, VMAPermission::ReadExecute, MemoryState::Code);
- MapSegment(module_->rodata, VMAPermission::Read, MemoryState::Static);
- MapSegment(module_->data, VMAPermission::ReadWrite, MemoryState::Static);
+ MapSegment(module_->code, VMAPermission::ReadExecute, MemoryState::CodeStatic);
+ MapSegment(module_->rodata, VMAPermission::Read, MemoryState::CodeMutable);
+ MapSegment(module_->data, VMAPermission::ReadWrite, MemoryState::CodeMutable);
}
VAddr Process::GetLinearHeapAreaAddress() const {
@@ -184,6 +184,8 @@ ResultVal<VAddr> Process::HeapAllocate(VAddr target, u64 size, VMAPermission per
// Initialize heap
heap_memory = std::make_shared<std::vector<u8>>();
heap_start = heap_end = target;
+ } else {
+ vm_manager.UnmapRange(heap_start, heap_end - heap_start);
}
// If necessary, expand backing vector to cover new heap extents.
@@ -203,7 +205,7 @@ ResultVal<VAddr> Process::HeapAllocate(VAddr target, u64 size, VMAPermission per
size, MemoryState::Heap));
vm_manager.Reprotect(vma, perms);
- heap_used += size;
+ heap_used = size;
memory_region->used += size;
return MakeResult<VAddr>(heap_end - size);
@@ -290,7 +292,7 @@ ResultCode Process::MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size) {
CASCADE_RESULT(auto new_vma,
vm_manager.MapMemoryBlock(dst_addr, backing_block, backing_block_offset, size,
- vma->second.meminfo_state));
+ MemoryState::Mapped));
// Protect mirror with permissions from old region
vm_manager.Reprotect(new_vma, vma->second.permissions);
// Remove permissions from old region
@@ -321,5 +323,4 @@ SharedPtr<Process> GetProcessById(u32 process_id) {
return *itr;
}
-SharedPtr<Process> g_current_process;
} // namespace Kernel
diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h
index add98472f..68e77a4d1 100644
--- a/src/core/hle/kernel/process.h
+++ b/src/core/hle/kernel/process.h
@@ -56,7 +56,7 @@ class ResourceLimit;
struct MemoryRegionInfo;
struct CodeSet final : public Object {
- static SharedPtr<CodeSet> Create(std::string name, u64 program_id);
+ static SharedPtr<CodeSet> Create(std::string name);
std::string GetTypeName() const override {
return "CodeSet";
@@ -72,8 +72,6 @@ struct CodeSet final : public Object {
/// Name of the process
std::string name;
- /// Title ID corresponding to the process
- u64 program_id;
std::shared_ptr<std::vector<u8>> memory;
@@ -113,6 +111,9 @@ public:
static u32 next_process_id;
+ /// Title ID corresponding to the process
+ u64 program_id;
+
/// Resource limit descriptor for this process
SharedPtr<ResourceLimit> resource_limit;
@@ -202,5 +203,4 @@ void ClearProcessList();
/// Retrieves a process from the current list of processes.
SharedPtr<Process> GetProcessById(u32 process_id);
-extern SharedPtr<Process> g_current_process;
} // namespace Kernel
diff --git a/src/core/hle/kernel/resource_limit.cpp b/src/core/hle/kernel/resource_limit.cpp
index 0149a3ed6..0ef5fc57d 100644
--- a/src/core/hle/kernel/resource_limit.cpp
+++ b/src/core/hle/kernel/resource_limit.cpp
@@ -29,62 +29,62 @@ SharedPtr<ResourceLimit> ResourceLimit::GetForCategory(ResourceLimitCategory cat
case ResourceLimitCategory::OTHER:
return resource_limits[static_cast<u8>(category)];
default:
- LOG_CRITICAL(Kernel, "Unknown resource limit category");
+ NGLOG_CRITICAL(Kernel, "Unknown resource limit category");
UNREACHABLE();
}
}
-s32 ResourceLimit::GetCurrentResourceValue(u32 resource) const {
+s32 ResourceLimit::GetCurrentResourceValue(ResourceType resource) const {
switch (resource) {
- case COMMIT:
+ case ResourceType::Commit:
return current_commit;
- case THREAD:
+ case ResourceType::Thread:
return current_threads;
- case EVENT:
+ case ResourceType::Event:
return current_events;
- case MUTEX:
+ case ResourceType::Mutex:
return current_mutexes;
- case SEMAPHORE:
+ case ResourceType::Semaphore:
return current_semaphores;
- case TIMER:
+ case ResourceType::Timer:
return current_timers;
- case SHARED_MEMORY:
+ case ResourceType::SharedMemory:
return current_shared_mems;
- case ADDRESS_ARBITER:
+ case ResourceType::AddressArbiter:
return current_address_arbiters;
- case CPU_TIME:
+ case ResourceType::CPUTime:
return current_cpu_time;
default:
- LOG_ERROR(Kernel, "Unknown resource type=%08X", resource);
+ NGLOG_ERROR(Kernel, "Unknown resource type={:08X}", static_cast<u32>(resource));
UNIMPLEMENTED();
return 0;
}
}
-u32 ResourceLimit::GetMaxResourceValue(u32 resource) const {
+u32 ResourceLimit::GetMaxResourceValue(ResourceType resource) const {
switch (resource) {
- case PRIORITY:
+ case ResourceType::Priority:
return max_priority;
- case COMMIT:
+ case ResourceType::Commit:
return max_commit;
- case THREAD:
+ case ResourceType::Thread:
return max_threads;
- case EVENT:
+ case ResourceType::Event:
return max_events;
- case MUTEX:
+ case ResourceType::Mutex:
return max_mutexes;
- case SEMAPHORE:
+ case ResourceType::Semaphore:
return max_semaphores;
- case TIMER:
+ case ResourceType::Timer:
return max_timers;
- case SHARED_MEMORY:
+ case ResourceType::SharedMemory:
return max_shared_mems;
- case ADDRESS_ARBITER:
+ case ResourceType::AddressArbiter:
return max_address_arbiters;
- case CPU_TIME:
+ case ResourceType::CPUTime:
return max_cpu_time;
default:
- LOG_ERROR(Kernel, "Unknown resource type=%08X", resource);
+ NGLOG_ERROR(Kernel, "Unknown resource type={:08X}", static_cast<u32>(resource));
UNIMPLEMENTED();
return 0;
}
diff --git a/src/core/hle/kernel/resource_limit.h b/src/core/hle/kernel/resource_limit.h
index 1a0ca11f1..cc689a27a 100644
--- a/src/core/hle/kernel/resource_limit.h
+++ b/src/core/hle/kernel/resource_limit.h
@@ -16,17 +16,17 @@ enum class ResourceLimitCategory : u8 {
OTHER = 3
};
-enum ResourceTypes {
- PRIORITY = 0,
- COMMIT = 1,
- THREAD = 2,
- EVENT = 3,
- MUTEX = 4,
- SEMAPHORE = 5,
- TIMER = 6,
- SHARED_MEMORY = 7,
- ADDRESS_ARBITER = 8,
- CPU_TIME = 9,
+enum class ResourceType {
+ Priority = 0,
+ Commit = 1,
+ Thread = 2,
+ Event = 3,
+ Mutex = 4,
+ Semaphore = 5,
+ Timer = 6,
+ SharedMemory = 7,
+ AddressArbiter = 8,
+ CPUTime = 9,
};
class ResourceLimit final : public Object {
@@ -60,14 +60,14 @@ public:
* @param resource Requested resource type
* @returns The current value of the resource type
*/
- s32 GetCurrentResourceValue(u32 resource) const;
+ s32 GetCurrentResourceValue(ResourceType resource) const;
/**
* Gets the max value for the specified resource.
* @param resource Requested resource type
* @returns The max value of the resource type
*/
- u32 GetMaxResourceValue(u32 resource) const;
+ u32 GetMaxResourceValue(ResourceType resource) const;
/// Name of resource limit object.
std::string name;
diff --git a/src/core/hle/kernel/scheduler.cpp b/src/core/hle/kernel/scheduler.cpp
index 235068b22..9cb9e0e5c 100644
--- a/src/core/hle/kernel/scheduler.cpp
+++ b/src/core/hle/kernel/scheduler.cpp
@@ -2,12 +2,15 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include "core/core.h"
#include "core/core_timing.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/scheduler.h"
namespace Kernel {
+std::mutex Scheduler::scheduler_mutex;
+
Scheduler::Scheduler(ARM_Interface* cpu_core) : cpu_core(cpu_core) {}
Scheduler::~Scheduler() {
@@ -17,6 +20,7 @@ Scheduler::~Scheduler() {
}
bool Scheduler::HaveReadyThreads() {
+ std::lock_guard<std::mutex> lock(scheduler_mutex);
return ready_queue.get_first() != nullptr;
}
@@ -67,7 +71,7 @@ void Scheduler::SwitchContext(Thread* new_thread) {
// Cancel any outstanding wakeup events for this thread
new_thread->CancelWakeupTimer();
- auto previous_process = Kernel::g_current_process;
+ auto previous_process = Core::CurrentProcess();
current_thread = new_thread;
@@ -75,8 +79,8 @@ void Scheduler::SwitchContext(Thread* new_thread) {
new_thread->status = THREADSTATUS_RUNNING;
if (previous_process != current_thread->owner_process) {
- Kernel::g_current_process = current_thread->owner_process;
- SetCurrentPageTable(&Kernel::g_current_process->vm_manager.page_table);
+ Core::CurrentProcess() = current_thread->owner_process;
+ SetCurrentPageTable(&Core::CurrentProcess()->vm_manager.page_table);
}
cpu_core->LoadContext(new_thread->context);
@@ -89,41 +93,53 @@ void Scheduler::SwitchContext(Thread* new_thread) {
}
void Scheduler::Reschedule() {
+ std::lock_guard<std::mutex> lock(scheduler_mutex);
+
Thread* cur = GetCurrentThread();
Thread* next = PopNextReadyThread();
if (cur && next) {
- LOG_TRACE(Kernel, "context switch %u -> %u", cur->GetObjectId(), next->GetObjectId());
+ NGLOG_TRACE(Kernel, "context switch {} -> {}", cur->GetObjectId(), next->GetObjectId());
} else if (cur) {
- LOG_TRACE(Kernel, "context switch %u -> idle", cur->GetObjectId());
+ NGLOG_TRACE(Kernel, "context switch {} -> idle", cur->GetObjectId());
} else if (next) {
- LOG_TRACE(Kernel, "context switch idle -> %u", next->GetObjectId());
+ NGLOG_TRACE(Kernel, "context switch idle -> {}", next->GetObjectId());
}
SwitchContext(next);
}
void Scheduler::AddThread(SharedPtr<Thread> thread, u32 priority) {
+ std::lock_guard<std::mutex> lock(scheduler_mutex);
+
thread_list.push_back(thread);
ready_queue.prepare(priority);
}
void Scheduler::RemoveThread(Thread* thread) {
+ std::lock_guard<std::mutex> 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);
+
ASSERT(thread->status == THREADSTATUS_READY);
ready_queue.push_back(priority, thread);
}
void Scheduler::UnscheduleThread(Thread* thread, u32 priority) {
+ std::lock_guard<std::mutex> lock(scheduler_mutex);
+
ASSERT(thread->status == THREADSTATUS_READY);
ready_queue.remove(priority, thread);
}
void Scheduler::SetThreadPriority(Thread* thread, u32 priority) {
+ std::lock_guard<std::mutex> lock(scheduler_mutex);
+
// If thread was ready, adjust queues
if (thread->status == THREADSTATUS_READY)
ready_queue.move(thread, thread->current_priority, priority);
diff --git a/src/core/hle/kernel/scheduler.h b/src/core/hle/kernel/scheduler.h
index 27d0247d6..a3b5fb8ca 100644
--- a/src/core/hle/kernel/scheduler.h
+++ b/src/core/hle/kernel/scheduler.h
@@ -4,6 +4,7 @@
#pragma once
+#include <mutex>
#include <vector>
#include "common/common_types.h"
#include "common/thread_queue_list.h"
@@ -68,6 +69,8 @@ private:
SharedPtr<Thread> current_thread = nullptr;
ARM_Interface* cpu_core;
+
+ static std::mutex scheduler_mutex;
};
} // namespace Kernel
diff --git a/src/core/hle/kernel/server_session.cpp b/src/core/hle/kernel/server_session.cpp
index 5f31cf19b..bf812c543 100644
--- a/src/core/hle/kernel/server_session.cpp
+++ b/src/core/hle/kernel/server_session.cpp
@@ -4,6 +4,7 @@
#include <tuple>
+#include "core/core.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/client_port.h"
#include "core/hle/kernel/client_session.h"
@@ -60,6 +61,9 @@ void ServerSession::Acquire(Thread* thread) {
ResultCode ServerSession::HandleDomainSyncRequest(Kernel::HLERequestContext& context) {
auto& 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) {
@@ -67,7 +71,7 @@ ResultCode ServerSession::HandleDomainSyncRequest(Kernel::HLERequestContext& con
return domain_request_handlers[object_id - 1]->HandleSyncRequest(context);
case IPC::DomainMessageHeader::CommandType::CloseVirtualHandle: {
- LOG_DEBUG(IPC, "CloseVirtualHandle, object_id=0x%08X", object_id);
+ NGLOG_DEBUG(IPC, "CloseVirtualHandle, object_id=0x{:08X}", object_id);
domain_request_handlers[object_id - 1] = nullptr;
@@ -77,7 +81,8 @@ ResultCode ServerSession::HandleDomainSyncRequest(Kernel::HLERequestContext& con
}
}
- LOG_CRITICAL(IPC, "Unknown domain command=%d", domain_message_header->command.Value());
+ NGLOG_CRITICAL(IPC, "Unknown domain command={}",
+ static_cast<int>(domain_message_header->command.Value()));
ASSERT(false);
}
@@ -91,7 +96,7 @@ ResultCode ServerSession::HandleSyncRequest(SharedPtr<Thread> thread) {
Kernel::HLERequestContext context(this);
u32* cmd_buf = (u32*)Memory::GetPointer(thread->GetTLSAddress());
- context.PopulateFromIncomingCommandBuffer(cmd_buf, *Kernel::g_current_process,
+ context.PopulateFromIncomingCommandBuffer(cmd_buf, *Core::CurrentProcess(),
Kernel::g_handle_table);
ResultCode result = RESULT_SUCCESS;
diff --git a/src/core/hle/kernel/shared_memory.cpp b/src/core/hle/kernel/shared_memory.cpp
index d4505061e..ac4921298 100644
--- a/src/core/hle/kernel/shared_memory.cpp
+++ b/src/core/hle/kernel/shared_memory.cpp
@@ -4,6 +4,7 @@
#include <cstring>
#include "common/logging/log.h"
+#include "core/core.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/memory.h"
#include "core/hle/kernel/shared_memory.h"
@@ -51,8 +52,8 @@ SharedPtr<SharedMemory> SharedMemory::Create(SharedPtr<Process> owner_process, u
}
// Refresh the address mappings for the current process.
- if (Kernel::g_current_process != nullptr) {
- Kernel::g_current_process->vm_manager.RefreshMemoryBlockMappings(linheap_memory.get());
+ if (Core::CurrentProcess() != nullptr) {
+ Core::CurrentProcess()->vm_manager.RefreshMemoryBlockMappings(linheap_memory.get());
}
} else {
auto& vm_manager = shared_memory->owner_process->vm_manager;
@@ -106,31 +107,19 @@ ResultCode SharedMemory::Map(Process* target_process, VAddr address, MemoryPermi
// Error out if the requested permissions don't match what the creator process allows.
if (static_cast<u32>(permissions) & ~static_cast<u32>(own_other_permissions)) {
- LOG_ERROR(Kernel, "cannot map id=%u, address=0x%llx name=%s, permissions don't match",
- GetObjectId(), address, name.c_str());
+ NGLOG_ERROR(Kernel, "cannot map id={}, address=0x{:X} name={}, permissions don't match",
+ GetObjectId(), address, name);
return ERR_INVALID_COMBINATION;
}
// Error out if the provided permissions are not compatible with what the creator process needs.
if (other_permissions != MemoryPermission::DontCare &&
static_cast<u32>(this->permissions) & ~static_cast<u32>(other_permissions)) {
- LOG_ERROR(Kernel, "cannot map id=%u, address=0x%llx name=%s, permissions don't match",
- GetObjectId(), address, name.c_str());
+ NGLOG_ERROR(Kernel, "cannot map id={}, address=0x{:X} name={}, permissions don't match",
+ GetObjectId(), address, name);
return ERR_WRONG_PERMISSION;
}
- // TODO(Subv): The same process that created a SharedMemory object
- // can not map it in its own address space unless it was created with addr=0, result 0xD900182C.
-
- if (address != 0) {
- // TODO(shinyquagsire23): Check for virtual/mappable memory here too?
- if (address >= Memory::HEAP_VADDR && address < Memory::HEAP_VADDR_END) {
- LOG_ERROR(Kernel, "cannot map id=%u, address=0x%llx name=%s, invalid address",
- GetObjectId(), address, name.c_str());
- return ERR_INVALID_ADDRESS;
- }
- }
-
VAddr target_address = address;
if (base_address == 0 && target_address == 0) {
@@ -142,10 +131,10 @@ ResultCode SharedMemory::Map(Process* target_process, VAddr address, MemoryPermi
auto result = target_process->vm_manager.MapMemoryBlock(
target_address, backing_block, backing_block_offset, size, MemoryState::Shared);
if (result.Failed()) {
- LOG_ERROR(
+ NGLOG_ERROR(
Kernel,
- "cannot map id=%u, target_address=0x%llx name=%s, error mapping to virtual memory",
- GetObjectId(), target_address, name.c_str());
+ "cannot map id={}, target_address=0x{:X} name={}, error mapping to virtual memory",
+ GetObjectId(), target_address, name);
return result.Code();
}
@@ -163,7 +152,7 @@ VMAPermission SharedMemory::ConvertPermissions(MemoryPermission permission) {
u32 masked_permissions =
static_cast<u32>(permission) & static_cast<u32>(MemoryPermission::ReadWriteExecute);
return static_cast<VMAPermission>(masked_permissions);
-};
+}
u8* SharedMemory::GetPointer(u32 offset) {
return backing_block->data() + backing_block_offset + offset;
diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp
index b2e8b5ce3..097416e9e 100644
--- a/src/core/hle/kernel/svc.cpp
+++ b/src/core/hle/kernel/svc.cpp
@@ -4,14 +4,15 @@
#include <algorithm>
#include <cinttypes>
+#include <iterator>
#include "common/logging/log.h"
#include "common/microprofile.h"
#include "common/string_util.h"
+#include "core/core.h"
#include "core/core_timing.h"
#include "core/hle/kernel/client_port.h"
#include "core/hle/kernel/client_session.h"
-#include "core/hle/kernel/condition_variable.h"
#include "core/hle/kernel/event.h"
#include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/mutex.h"
@@ -30,30 +31,30 @@ namespace Kernel {
/// 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) {
- LOG_TRACE(Kernel_SVC, "called, heap_size=0x%llx", heap_size);
- auto& process = *g_current_process;
+ NGLOG_TRACE(Kernel_SVC, "called, heap_size=0x{:X}", heap_size);
+ auto& process = *Core::CurrentProcess();
CASCADE_RESULT(*heap_addr,
process.HeapAllocate(Memory::HEAP_VADDR, heap_size, VMAPermission::ReadWrite));
return RESULT_SUCCESS;
}
static ResultCode SetMemoryAttribute(VAddr addr, u64 size, u32 state0, u32 state1) {
- LOG_WARNING(Kernel_SVC, "(STUBBED) called, addr=0x%llx", addr);
+ NGLOG_WARNING(Kernel_SVC, "(STUBBED) called, addr=0x{:X}", addr);
return RESULT_SUCCESS;
}
/// Maps a memory range into a different range.
static ResultCode MapMemory(VAddr dst_addr, VAddr src_addr, u64 size) {
- LOG_TRACE(Kernel_SVC, "called, dst_addr=0x%llx, src_addr=0x%llx, size=0x%llx", dst_addr,
- src_addr, size);
- return g_current_process->MirrorMemory(dst_addr, src_addr, size);
+ NGLOG_TRACE(Kernel_SVC, "called, dst_addr=0x{:X}, src_addr=0x{:X}, size=0x{:X}", dst_addr,
+ src_addr, size);
+ return Core::CurrentProcess()->MirrorMemory(dst_addr, src_addr, size);
}
/// Unmaps a region that was previously mapped with svcMapMemory
static ResultCode UnmapMemory(VAddr dst_addr, VAddr src_addr, u64 size) {
- LOG_TRACE(Kernel_SVC, "called, dst_addr=0x%llx, src_addr=0x%llx, size=0x%llx", dst_addr,
- src_addr, size);
- return g_current_process->UnmapMemory(dst_addr, src_addr, size);
+ NGLOG_TRACE(Kernel_SVC, "called, dst_addr=0x{:X}, src_addr=0x{:X}, size=0x{:X}", dst_addr,
+ src_addr, size);
+ return Core::CurrentProcess()->UnmapMemory(dst_addr, src_addr, size);
}
/// Connect to an OS service given the port name, returns the handle to the port to out
@@ -67,11 +68,11 @@ static ResultCode ConnectToNamedPort(Handle* out_handle, VAddr port_name_address
if (port_name.size() > PortNameMaxLength)
return ERR_PORT_NAME_TOO_LONG;
- LOG_TRACE(Kernel_SVC, "called port_name=%s", port_name.c_str());
+ NGLOG_TRACE(Kernel_SVC, "called port_name={}", port_name);
auto it = Service::g_kernel_named_ports.find(port_name);
if (it == Service::g_kernel_named_ports.end()) {
- LOG_WARNING(Kernel_SVC, "tried to connect to unknown port: %s", port_name.c_str());
+ NGLOG_WARNING(Kernel_SVC, "tried to connect to unknown port: {}", port_name);
return ERR_NOT_FOUND;
}
@@ -89,11 +90,11 @@ static ResultCode ConnectToNamedPort(Handle* out_handle, VAddr port_name_address
static ResultCode SendSyncRequest(Handle handle) {
SharedPtr<ClientSession> session = g_handle_table.Get<ClientSession>(handle);
if (!session) {
- LOG_ERROR(Kernel_SVC, "called with invalid handle=0x%08X", handle);
+ NGLOG_ERROR(Kernel_SVC, "called with invalid handle=0x{:08X}", handle);
return ERR_INVALID_HANDLE;
}
- LOG_TRACE(Kernel_SVC, "called handle=0x%08X(%s)", handle, session->GetName().c_str());
+ NGLOG_TRACE(Kernel_SVC, "called handle=0x{:08X}({})", handle, session->GetName());
Core::System::GetInstance().PrepareReschedule();
@@ -104,7 +105,7 @@ static ResultCode SendSyncRequest(Handle handle) {
/// Get the ID for the specified thread.
static ResultCode GetThreadId(u32* thread_id, Handle thread_handle) {
- LOG_TRACE(Kernel_SVC, "called thread=0x%08X", thread_handle);
+ NGLOG_TRACE(Kernel_SVC, "called thread=0x{:08X}", thread_handle);
const SharedPtr<Thread> thread = g_handle_table.Get<Thread>(thread_handle);
if (!thread) {
@@ -117,7 +118,7 @@ static ResultCode GetThreadId(u32* thread_id, Handle thread_handle) {
/// Get the ID of the specified process
static ResultCode GetProcessId(u32* process_id, Handle process_handle) {
- LOG_TRACE(Kernel_SVC, "called process=0x%08X", process_handle);
+ NGLOG_TRACE(Kernel_SVC, "called process=0x{:08X}", process_handle);
const SharedPtr<Process> process = g_handle_table.Get<Process>(process_handle);
if (!process) {
@@ -144,41 +145,11 @@ static bool DefaultThreadWakeupCallback(ThreadWakeupReason reason, SharedPtr<Thr
return true;
};
-/// Wait for a kernel object to synchronize, timeout after the specified nanoseconds
-static ResultCode WaitSynchronization1(
- SharedPtr<WaitObject> object, Thread* thread, s64 nano_seconds = -1,
- std::function<Thread::WakeupCallback> wakeup_callback = DefaultThreadWakeupCallback) {
-
- if (!object) {
- return ERR_INVALID_HANDLE;
- }
-
- if (object->ShouldWait(thread)) {
- if (nano_seconds == 0) {
- return RESULT_TIMEOUT;
- }
-
- thread->wait_objects = {object};
- object->AddWaitingThread(thread);
- thread->status = THREADSTATUS_WAIT_SYNCH_ANY;
-
- // Create an event to wake the thread up after the specified nanosecond delay has passed
- thread->WakeAfterDelay(nano_seconds);
- thread->wakeup_callback = wakeup_callback;
-
- Core::System::GetInstance().PrepareReschedule();
- } else {
- object->Acquire(thread);
- }
-
- return RESULT_SUCCESS;
-}
-
/// 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) {
- LOG_TRACE(Kernel_SVC, "called handles_address=0x%llx, handle_count=%d, nano_seconds=%d",
- handles_address, handle_count, nano_seconds);
+ NGLOG_TRACE(Kernel_SVC, "called handles_address=0x{:X}, handle_count={}, nano_seconds={}",
+ handles_address, handle_count, nano_seconds);
if (!Memory::IsValidVirtualAddress(handles_address))
return ERR_INVALID_POINTER;
@@ -231,14 +202,14 @@ static ResultCode WaitSynchronization(Handle* index, VAddr handles_address, u64
thread->WakeAfterDelay(nano_seconds);
thread->wakeup_callback = DefaultThreadWakeupCallback;
- Core::System::GetInstance().PrepareReschedule();
+ Core::System::GetInstance().CpuCore(thread->processor_id).PrepareReschedule();
return RESULT_TIMEOUT;
}
/// Resumes a thread waiting on WaitSynchronization
static ResultCode CancelSynchronization(Handle thread_handle) {
- LOG_TRACE(Kernel_SVC, "called thread=0x%08X", thread_handle);
+ NGLOG_TRACE(Kernel_SVC, "called thread=0x{:X}", thread_handle);
const SharedPtr<Thread> thread = g_handle_table.Get<Thread>(thread_handle);
if (!thread) {
@@ -255,74 +226,56 @@ static ResultCode CancelSynchronization(Handle thread_handle) {
/// 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) {
- LOG_TRACE(Kernel_SVC,
- "called holding_thread_handle=0x%08X, mutex_addr=0x%llx, "
- "requesting_current_thread_handle=0x%08X",
- holding_thread_handle, mutex_addr, requesting_thread_handle);
-
- SharedPtr<Thread> holding_thread = g_handle_table.Get<Thread>(holding_thread_handle);
- SharedPtr<Thread> requesting_thread = g_handle_table.Get<Thread>(requesting_thread_handle);
-
- ASSERT(requesting_thread);
- ASSERT(requesting_thread == GetCurrentThread());
-
- SharedPtr<Mutex> mutex = g_object_address_table.Get<Mutex>(mutex_addr);
- if (!mutex) {
- // Create a new mutex for the specified address if one does not already exist
- mutex = Mutex::Create(holding_thread, mutex_addr);
- mutex->name = Common::StringFromFormat("mutex-%llx", mutex_addr);
- }
-
- ASSERT(holding_thread == mutex->GetHoldingThread());
+ NGLOG_TRACE(Kernel_SVC,
+ "called holding_thread_handle=0x{:08X}, mutex_addr=0x{:X}, "
+ "requesting_current_thread_handle=0x{:08X}",
+ holding_thread_handle, mutex_addr, requesting_thread_handle);
- return WaitSynchronization1(mutex, requesting_thread.get());
+ return Mutex::TryAcquire(mutex_addr, holding_thread_handle, requesting_thread_handle);
}
/// Unlock a mutex
static ResultCode ArbitrateUnlock(VAddr mutex_addr) {
- LOG_TRACE(Kernel_SVC, "called mutex_addr=0x%llx", mutex_addr);
-
- SharedPtr<Mutex> mutex = g_object_address_table.Get<Mutex>(mutex_addr);
- ASSERT(mutex);
+ NGLOG_TRACE(Kernel_SVC, "called mutex_addr=0x{:X}", mutex_addr);
- return mutex->Release(GetCurrentThread());
+ return Mutex::Release(mutex_addr);
}
/// Break program execution
static void Break(u64 unk_0, u64 unk_1, u64 unk_2) {
- LOG_CRITICAL(Debug_Emulated, "Emulated program broke execution!");
+ NGLOG_CRITICAL(Debug_Emulated, "Emulated program broke execution!");
ASSERT(false);
}
/// Used to output a message on a debug hardware unit - does nothing on a retail unit
static void OutputDebugString(VAddr address, s32 len) {
- std::vector<char> string(len);
- Memory::ReadBlock(address, string.data(), len);
- LOG_DEBUG(Debug_Emulated, "%.*s", len, string.data());
+ std::string str(len, '\0');
+ Memory::ReadBlock(address, str.data(), str.size());
+ NGLOG_DEBUG(Debug_Emulated, "{}", str);
}
/// Gets system/memory information for the current process
static ResultCode GetInfo(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);
+ NGLOG_TRACE(Kernel_SVC, "called info_id=0x{:X}, info_sub_id=0x{:X}, handle=0x{:08X}", info_id,
+ info_sub_id, handle);
- auto& vm_manager = g_current_process->vm_manager;
+ auto& vm_manager = Core::CurrentProcess()->vm_manager;
switch (static_cast<GetInfoType>(info_id)) {
case GetInfoType::AllowedCpuIdBitmask:
- *result = g_current_process->allowed_processor_mask;
+ *result = Core::CurrentProcess()->allowed_processor_mask;
break;
case GetInfoType::AllowedThreadPrioBitmask:
- *result = g_current_process->allowed_thread_priority_mask;
+ *result = Core::CurrentProcess()->allowed_thread_priority_mask;
break;
case GetInfoType::MapRegionBaseAddr:
- *result = vm_manager.GetMapRegionBaseAddr();
+ *result = Memory::MAP_REGION_VADDR;
break;
case GetInfoType::MapRegionSize:
- *result = vm_manager.GetAddressSpaceSize();
+ *result = Memory::MAP_REGION_SIZE;
break;
case GetInfoType::HeapRegionBaseAddr:
- *result = vm_manager.GetNewMapRegionBaseAddr() + vm_manager.GetNewMapRegionSize();
+ *result = Memory::HEAP_VADDR;
break;
case GetInfoType::HeapRegionSize:
*result = Memory::HEAP_SIZE;
@@ -346,21 +299,21 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id)
*result = vm_manager.GetAddressSpaceSize();
break;
case GetInfoType::NewMapRegionBaseAddr:
- *result = vm_manager.GetNewMapRegionBaseAddr();
+ *result = Memory::NEW_MAP_REGION_VADDR;
break;
case GetInfoType::NewMapRegionSize:
- *result = vm_manager.GetNewMapRegionSize();
+ *result = Memory::NEW_MAP_REGION_SIZE;
break;
case GetInfoType::IsVirtualAddressMemoryEnabled:
- *result = g_current_process->is_virtual_address_memory_enabled;
+ *result = Core::CurrentProcess()->is_virtual_address_memory_enabled;
break;
case GetInfoType::TitleId:
- LOG_WARNING(Kernel_SVC, "(STUBBED) Attempted to query titleid, returned 0");
+ NGLOG_WARNING(Kernel_SVC, "(STUBBED) Attempted to query titleid, returned 0");
*result = 0;
break;
case GetInfoType::PrivilegedProcessId:
- LOG_WARNING(Kernel_SVC,
- "(STUBBED) Attempted to query priviledged process id bounds, returned 0");
+ NGLOG_WARNING(Kernel_SVC,
+ "(STUBBED) Attempted to query privileged process id bounds, returned 0");
*result = 0;
break;
case GetInfoType::UserExceptionContextAddr:
@@ -375,6 +328,19 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id)
return RESULT_SUCCESS;
}
+/// Sets the thread activity
+static ResultCode SetThreadActivity(Handle handle, u32 unknown) {
+ NGLOG_WARNING(Kernel_SVC, "(STUBBED) called, handle=0x{:08X}, unknown=0x{:08X}", handle,
+ unknown);
+ return RESULT_SUCCESS;
+}
+
+/// Gets the thread context
+static ResultCode GetThreadContext(Handle handle, VAddr addr) {
+ NGLOG_WARNING(Kernel_SVC, "(STUBBED) called, handle=0x{:08X}, addr=0x{:X}", handle, addr);
+ return RESULT_SUCCESS;
+}
+
/// Gets the priority for the specified thread
static ResultCode GetThreadPriority(u32* priority, Handle handle) {
const SharedPtr<Thread> thread = g_handle_table.Get<Thread>(handle);
@@ -397,33 +363,29 @@ static ResultCode SetThreadPriority(Handle handle, u32 priority) {
// Note: The kernel uses the current process's resource limit instead of
// the one from the thread owner's resource limit.
- SharedPtr<ResourceLimit>& resource_limit = g_current_process->resource_limit;
- if (resource_limit->GetMaxResourceValue(ResourceTypes::PRIORITY) > priority) {
+ SharedPtr<ResourceLimit>& resource_limit = Core::CurrentProcess()->resource_limit;
+ if (resource_limit->GetMaxResourceValue(ResourceType::Priority) > priority) {
return ERR_NOT_AUTHORIZED;
}
thread->SetPriority(priority);
- thread->UpdatePriority();
- // Update the mutexes that this thread is waiting for
- for (auto& mutex : thread->pending_mutexes)
- mutex->UpdatePriority();
-
- Core::System::GetInstance().PrepareReschedule();
+ Core::System::GetInstance().CpuCore(thread->processor_id).PrepareReschedule();
return RESULT_SUCCESS;
}
/// Get which CPU core is executing the current thread
static u32 GetCurrentProcessorNumber() {
- LOG_WARNING(Kernel_SVC, "(STUBBED) called, defaulting to processor 0");
- return 0;
+ NGLOG_TRACE(Kernel_SVC, "called");
+ return GetCurrentThread()->processor_id;
}
static ResultCode MapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 size,
u32 permissions) {
- LOG_TRACE(Kernel_SVC,
- "called, shared_memory_handle=0x%08X, addr=0x%llx, size=0x%llx, permissions=0x%08X",
- shared_memory_handle, addr, size, permissions);
+ NGLOG_TRACE(
+ Kernel_SVC,
+ "called, shared_memory_handle=0x{:X}, addr=0x{:X}, size=0x{:X}, permissions=0x{:08X}",
+ shared_memory_handle, addr, size, permissions);
SharedPtr<SharedMemory> shared_memory = g_handle_table.Get<SharedMemory>(shared_memory_handle);
if (!shared_memory) {
@@ -440,23 +402,22 @@ static ResultCode MapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 s
case MemoryPermission::WriteExecute:
case MemoryPermission::ReadWriteExecute:
case MemoryPermission::DontCare:
- return shared_memory->Map(g_current_process.get(), addr, permissions_type,
+ return shared_memory->Map(Core::CurrentProcess().get(), addr, permissions_type,
MemoryPermission::DontCare);
default:
- LOG_ERROR(Kernel_SVC, "unknown permissions=0x%08X", permissions);
+ NGLOG_ERROR(Kernel_SVC, "unknown permissions=0x{:08X}", permissions);
}
return RESULT_SUCCESS;
}
static ResultCode UnmapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 size) {
- LOG_WARNING(Kernel_SVC,
- "called, shared_memory_handle=0x%08X, addr=0x%" PRIx64 ", size=0x%" PRIx64 "",
- shared_memory_handle, addr, size);
+ NGLOG_WARNING(Kernel_SVC, "called, shared_memory_handle=0x{:08X}, addr=0x{:X}, size=0x{:X}",
+ shared_memory_handle, addr, size);
SharedPtr<SharedMemory> shared_memory = g_handle_table.Get<SharedMemory>(shared_memory_handle);
- return shared_memory->Unmap(g_current_process.get(), addr);
+ return shared_memory->Unmap(Core::CurrentProcess().get(), addr);
}
/// Query process memory
@@ -468,11 +429,11 @@ static ResultCode QueryProcessMemory(MemoryInfo* memory_info, PageInfo* /*page_i
}
auto vma = process->vm_manager.FindVMA(addr);
memory_info->attributes = 0;
- if (vma == g_current_process->vm_manager.vma_map.end()) {
+ if (vma == Core::CurrentProcess()->vm_manager.vma_map.end()) {
memory_info->base_address = 0;
memory_info->permission = static_cast<u32>(VMAPermission::None);
memory_info->size = 0;
- memory_info->type = static_cast<u32>(MemoryState::Free);
+ memory_info->type = static_cast<u32>(MemoryState::Unmapped);
} else {
memory_info->base_address = vma->second.base;
memory_info->permission = static_cast<u32>(vma->second.permissions);
@@ -480,40 +441,47 @@ static ResultCode QueryProcessMemory(MemoryInfo* memory_info, PageInfo* /*page_i
memory_info->type = static_cast<u32>(vma->second.meminfo_state);
}
- LOG_TRACE(Kernel_SVC, "called process=0x%08X addr=%llx", process_handle, addr);
+ NGLOG_TRACE(Kernel_SVC, "called process=0x{:08X} addr={:X}", process_handle, addr);
return RESULT_SUCCESS;
}
/// Query memory
static ResultCode QueryMemory(MemoryInfo* memory_info, PageInfo* page_info, VAddr addr) {
- LOG_TRACE(Kernel_SVC, "called, addr=%llx", addr);
+ NGLOG_TRACE(Kernel_SVC, "called, addr={:X}", addr);
return QueryProcessMemory(memory_info, page_info, CurrentProcess, addr);
}
/// Exits the current process
static void ExitProcess() {
- LOG_INFO(Kernel_SVC, "Process %u exiting", g_current_process->process_id);
+ NGLOG_INFO(Kernel_SVC, "Process {} exiting", Core::CurrentProcess()->process_id);
- ASSERT_MSG(g_current_process->status == ProcessStatus::Running, "Process has already exited");
+ ASSERT_MSG(Core::CurrentProcess()->status == ProcessStatus::Running,
+ "Process has already exited");
- g_current_process->status = ProcessStatus::Exited;
+ Core::CurrentProcess()->status = ProcessStatus::Exited;
- // Stop all the process threads that are currently waiting for objects.
- auto& thread_list = Core::System::GetInstance().Scheduler().GetThreadList();
- for (auto& thread : thread_list) {
- if (thread->owner_process != g_current_process)
- continue;
+ auto stop_threads = [](const std::vector<SharedPtr<Thread>>& thread_list) {
+ for (auto& thread : thread_list) {
+ if (thread->owner_process != Core::CurrentProcess())
+ continue;
- if (thread == GetCurrentThread())
- continue;
+ if (thread == GetCurrentThread())
+ continue;
- // TODO(Subv): When are the other running/ready threads terminated?
- ASSERT_MSG(thread->status == THREADSTATUS_WAIT_SYNCH_ANY ||
- thread->status == THREADSTATUS_WAIT_SYNCH_ALL,
- "Exiting processes with non-waiting threads is currently unimplemented");
+ // TODO(Subv): When are the other running/ready threads terminated?
+ ASSERT_MSG(thread->status == THREADSTATUS_WAIT_SYNCH_ANY ||
+ thread->status == THREADSTATUS_WAIT_SYNCH_ALL,
+ "Exiting processes with non-waiting threads is currently unimplemented");
- thread->Stop();
- }
+ thread->Stop();
+ }
+ };
+
+ auto& system = Core::System::GetInstance();
+ stop_threads(system.Scheduler(0)->GetThreadList());
+ stop_threads(system.Scheduler(1)->GetThreadList());
+ stop_threads(system.Scheduler(2)->GetThreadList());
+ stop_threads(system.Scheduler(3)->GetThreadList());
// Kill the current thread
GetCurrentThread()->Stop();
@@ -524,72 +492,71 @@ static void ExitProcess() {
/// Creates a new thread
static ResultCode CreateThread(Handle* out_handle, VAddr entry_point, u64 arg, VAddr stack_top,
u32 priority, s32 processor_id) {
- std::string name = Common::StringFromFormat("unknown-%llx", entry_point);
+ std::string name = fmt::format("unknown-{:X}", entry_point);
if (priority > THREADPRIO_LOWEST) {
return ERR_OUT_OF_RANGE;
}
- SharedPtr<ResourceLimit>& resource_limit = g_current_process->resource_limit;
- if (resource_limit->GetMaxResourceValue(ResourceTypes::PRIORITY) > priority) {
+ SharedPtr<ResourceLimit>& resource_limit = Core::CurrentProcess()->resource_limit;
+ if (resource_limit->GetMaxResourceValue(ResourceType::Priority) > priority) {
return ERR_NOT_AUTHORIZED;
}
if (processor_id == THREADPROCESSORID_DEFAULT) {
// Set the target CPU to the one specified in the process' exheader.
- processor_id = g_current_process->ideal_processor;
+ processor_id = Core::CurrentProcess()->ideal_processor;
ASSERT(processor_id != THREADPROCESSORID_DEFAULT);
}
switch (processor_id) {
case THREADPROCESSORID_0:
- break;
case THREADPROCESSORID_1:
case THREADPROCESSORID_2:
case THREADPROCESSORID_3:
- // TODO(bunnei): Implement support for other processor IDs
- LOG_ERROR(Kernel_SVC,
- "Newly created thread must run in another thread (%u), unimplemented.",
- processor_id);
break;
default:
- ASSERT_MSG(false, "Unsupported thread processor ID: %d", processor_id);
+ ASSERT_MSG(false, "Unsupported thread processor ID: {}", processor_id);
break;
}
CASCADE_RESULT(SharedPtr<Thread> thread,
Thread::Create(name, entry_point, priority, arg, processor_id, stack_top,
- g_current_process));
+ Core::CurrentProcess()));
CASCADE_RESULT(thread->guest_handle, g_handle_table.Create(thread));
*out_handle = thread->guest_handle;
Core::System::GetInstance().PrepareReschedule();
+ Core::System::GetInstance().CpuCore(thread->processor_id).PrepareReschedule();
- LOG_TRACE(Kernel_SVC,
- "called entrypoint=0x%08X (%s), arg=0x%08X, stacktop=0x%08X, "
- "threadpriority=0x%08X, processorid=0x%08X : created handle=0x%08X",
- entry_point, name.c_str(), arg, stack_top, priority, processor_id, *out_handle);
+ NGLOG_TRACE(Kernel_SVC,
+ "called entrypoint=0x{:08X} ({}), arg=0x{:08X}, stacktop=0x{:08X}, "
+ "threadpriority=0x{:08X}, processorid=0x{:08X} : created handle=0x{:08X}",
+ entry_point, name, arg, stack_top, priority, processor_id, *out_handle);
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);
+ NGLOG_TRACE(Kernel_SVC, "called thread=0x{:08X}", thread_handle);
const SharedPtr<Thread> thread = g_handle_table.Get<Thread>(thread_handle);
if (!thread) {
return ERR_INVALID_HANDLE;
}
+ ASSERT(thread->status == THREADSTATUS_DORMANT);
+
thread->ResumeFromWait();
+ Core::System::GetInstance().CpuCore(thread->processor_id).PrepareReschedule();
return RESULT_SUCCESS;
}
/// Called when a thread exits
static void ExitThread() {
- LOG_TRACE(Kernel_SVC, "called, pc=0x%08X", Core::CPU().GetPC());
+ NGLOG_TRACE(Kernel_SVC, "called, pc=0x{:08X}", Core::CurrentArmInterface().GetPC());
ExitCurrentThread();
Core::System::GetInstance().PrepareReschedule();
@@ -597,11 +564,11 @@ static void ExitThread() {
/// Sleep the current thread
static void SleepThread(s64 nanoseconds) {
- LOG_TRACE(Kernel_SVC, "called nanoseconds=%lld", nanoseconds);
+ NGLOG_TRACE(Kernel_SVC, "called nanoseconds={}", nanoseconds);
// Don't attempt to yield execution if there are no available threads to run,
// this way we avoid a useless reschedule to the idle thread.
- if (nanoseconds == 0 && !Core::System::GetInstance().Scheduler().HaveReadyThreads())
+ if (nanoseconds == 0 && !Core::System::GetInstance().CurrentScheduler().HaveReadyThreads())
return;
// Sleep current thread and check for next thread to schedule
@@ -616,111 +583,107 @@ static void SleepThread(s64 nanoseconds) {
/// Signal process wide key atomic
static ResultCode WaitProcessWideKeyAtomic(VAddr mutex_addr, VAddr condition_variable_addr,
Handle thread_handle, s64 nano_seconds) {
- LOG_TRACE(
+ NGLOG_TRACE(
Kernel_SVC,
- "called mutex_addr=%llx, condition_variable_addr=%llx, thread_handle=0x%08X, timeout=%d",
+ "called mutex_addr={:X}, condition_variable_addr={:X}, thread_handle=0x{:08X}, timeout={}",
mutex_addr, condition_variable_addr, thread_handle, nano_seconds);
SharedPtr<Thread> thread = g_handle_table.Get<Thread>(thread_handle);
ASSERT(thread);
- SharedPtr<Mutex> mutex = g_object_address_table.Get<Mutex>(mutex_addr);
- if (!mutex) {
- // Create a new mutex for the specified address if one does not already exist
- mutex = Mutex::Create(thread, mutex_addr);
- mutex->name = Common::StringFromFormat("mutex-%llx", mutex_addr);
- }
+ CASCADE_CODE(Mutex::Release(mutex_addr));
- SharedPtr<ConditionVariable> condition_variable =
- g_object_address_table.Get<ConditionVariable>(condition_variable_addr);
- if (!condition_variable) {
- // Create a new condition_variable for the specified address if one does not already exist
- condition_variable = ConditionVariable::Create(condition_variable_addr).Unwrap();
- condition_variable->name =
- Common::StringFromFormat("condition-variable-%llx", condition_variable_addr);
- }
+ SharedPtr<Thread> current_thread = GetCurrentThread();
+ current_thread->condvar_wait_address = condition_variable_addr;
+ current_thread->mutex_wait_address = mutex_addr;
+ current_thread->wait_handle = thread_handle;
+ current_thread->status = THREADSTATUS_WAIT_MUTEX;
+ current_thread->wakeup_callback = nullptr;
- if (condition_variable->mutex_addr) {
- // Previously created the ConditionVariable using WaitProcessWideKeyAtomic, verify
- // everything is correct
- ASSERT(condition_variable->mutex_addr == mutex_addr);
- } else {
- // Previously created the ConditionVariable using SignalProcessWideKey, set the mutex
- // associated with it
- condition_variable->mutex_addr = mutex_addr;
- }
+ current_thread->WakeAfterDelay(nano_seconds);
- if (mutex->GetOwnerHandle()) {
- // Release the mutex if the current thread is holding it
- mutex->Release(thread.get());
- }
+ // Note: Deliberately don't attempt to inherit the lock owner's priority.
- auto wakeup_callback = [mutex, nano_seconds](ThreadWakeupReason reason,
- SharedPtr<Thread> thread,
- SharedPtr<WaitObject> object, size_t index) {
- ASSERT(thread->status == THREADSTATUS_WAIT_SYNCH_ANY);
-
- if (reason == ThreadWakeupReason::Timeout) {
- thread->SetWaitSynchronizationResult(RESULT_TIMEOUT);
- return true;
- }
-
- ASSERT(reason == ThreadWakeupReason::Signal);
+ Core::System::GetInstance().CpuCore(current_thread->processor_id).PrepareReschedule();
+ return RESULT_SUCCESS;
+}
- // Now try to acquire the mutex and don't resume if it's not available.
- if (!mutex->ShouldWait(thread.get())) {
- mutex->Acquire(thread.get());
- thread->SetWaitSynchronizationResult(RESULT_SUCCESS);
- return true;
- }
+/// Signal process wide key
+static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target) {
+ NGLOG_TRACE(Kernel_SVC, "called, condition_variable_addr=0x{:X}, target=0x{:08X}",
+ condition_variable_addr, target);
+
+ auto RetrieveWaitingThreads =
+ [](size_t core_index, std::vector<SharedPtr<Thread>>& waiting_threads, VAddr condvar_addr) {
+ const auto& scheduler = Core::System::GetInstance().Scheduler(core_index);
+ auto& thread_list = scheduler->GetThreadList();
+
+ for (auto& thread : thread_list) {
+ if (thread->condvar_wait_address == condvar_addr)
+ waiting_threads.push_back(thread);
+ }
+ };
+
+ // Retrieve a list of all threads that are waiting for this condition variable.
+ std::vector<SharedPtr<Thread>> waiting_threads;
+ RetrieveWaitingThreads(0, waiting_threads, condition_variable_addr);
+ RetrieveWaitingThreads(1, waiting_threads, condition_variable_addr);
+ RetrieveWaitingThreads(2, waiting_threads, condition_variable_addr);
+ RetrieveWaitingThreads(3, waiting_threads, condition_variable_addr);
+ // Sort them by priority, such that the highest priority ones come first.
+ std::sort(waiting_threads.begin(), waiting_threads.end(),
+ [](const SharedPtr<Thread>& lhs, const SharedPtr<Thread>& rhs) {
+ return lhs->current_priority < rhs->current_priority;
+ });
+
+ // Only process up to 'target' threads, unless 'target' is -1, in which case process
+ // them all.
+ size_t last = waiting_threads.size();
+ if (target != -1)
+ last = target;
+
+ // If there are no threads waiting on this condition variable, just exit
+ if (last > waiting_threads.size())
+ return RESULT_SUCCESS;
- if (nano_seconds == 0) {
- thread->SetWaitSynchronizationResult(RESULT_TIMEOUT);
- return true;
- }
+ for (size_t index = 0; index < last; ++index) {
+ auto& thread = waiting_threads[index];
- thread->wait_objects = {mutex};
- mutex->AddWaitingThread(thread);
- thread->status = THREADSTATUS_WAIT_SYNCH_ANY;
+ ASSERT(thread->condvar_wait_address == condition_variable_addr);
- // Create an event to wake the thread up after the
- // specified nanosecond delay has passed
- thread->WakeAfterDelay(nano_seconds);
- thread->wakeup_callback = DefaultThreadWakeupCallback;
+ // If the mutex is not yet acquired, acquire it.
+ u32 mutex_val = Memory::Read32(thread->mutex_wait_address);
- Core::System::GetInstance().PrepareReschedule();
+ if (mutex_val == 0) {
+ // We were able to acquire the mutex, resume this thread.
+ Memory::Write32(thread->mutex_wait_address, thread->wait_handle);
+ ASSERT(thread->status == THREADSTATUS_WAIT_MUTEX);
+ thread->ResumeFromWait();
- return false;
- };
- CASCADE_CODE(
- WaitSynchronization1(condition_variable, thread.get(), nano_seconds, wakeup_callback));
+ auto lock_owner = thread->lock_owner;
+ if (lock_owner)
+ lock_owner->RemoveMutexWaiter(thread);
- return RESULT_SUCCESS;
-}
+ thread->lock_owner = nullptr;
+ thread->mutex_wait_address = 0;
+ thread->condvar_wait_address = 0;
+ thread->wait_handle = 0;
+ } else {
+ // Couldn't acquire the mutex, block the thread.
+ Handle owner_handle = static_cast<Handle>(mutex_val & Mutex::MutexOwnerMask);
+ auto owner = g_handle_table.Get<Thread>(owner_handle);
+ ASSERT(owner);
+ ASSERT(thread->status != THREADSTATUS_RUNNING);
+ thread->status = THREADSTATUS_WAIT_MUTEX;
+ thread->wakeup_callback = nullptr;
-/// Signal process wide key
-static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target) {
- LOG_TRACE(Kernel_SVC, "called, condition_variable_addr=0x%llx, target=0x%08x",
- condition_variable_addr, target);
-
- // Wakeup all or one thread - Any other value is unimplemented
- ASSERT(target == -1 || target == 1);
-
- SharedPtr<ConditionVariable> condition_variable =
- g_object_address_table.Get<ConditionVariable>(condition_variable_addr);
- if (!condition_variable) {
- // Create a new condition_variable for the specified address if one does not already exist
- condition_variable = ConditionVariable::Create(condition_variable_addr).Unwrap();
- condition_variable->name =
- Common::StringFromFormat("condition-variable-%llx", condition_variable_addr);
- }
+ // Signal that the mutex now has a waiting thread.
+ Memory::Write32(thread->mutex_wait_address, mutex_val | Mutex::MutexHasWaitersFlag);
- CASCADE_CODE(condition_variable->Release(target));
+ owner->AddMutexWaiter(thread);
- if (condition_variable->mutex_addr) {
- // If a mutex was created for this condition_variable, wait the current thread on it
- SharedPtr<Mutex> mutex = g_object_address_table.Get<Mutex>(condition_variable->mutex_addr);
- return WaitSynchronization1(mutex, GetCurrentThread());
+ Core::System::GetInstance().CpuCore(thread->processor_id).PrepareReschedule();
+ }
}
return RESULT_SUCCESS;
@@ -738,13 +701,13 @@ static u64 GetSystemTick() {
/// Close a handle
static ResultCode CloseHandle(Handle handle) {
- LOG_TRACE(Kernel_SVC, "Closing handle 0x%08X", handle);
+ NGLOG_TRACE(Kernel_SVC, "Closing handle 0x{:08X}", handle);
return g_handle_table.Close(handle);
}
/// Reset an event
static ResultCode ResetSignal(Handle handle) {
- LOG_WARNING(Kernel_SVC, "(STUBBED) called handle 0x%08X", handle);
+ NGLOG_WARNING(Kernel_SVC, "(STUBBED) called handle 0x{:08X}", handle);
auto event = g_handle_table.Get<Event>(handle);
ASSERT(event != nullptr);
event->Clear();
@@ -753,21 +716,69 @@ static ResultCode ResetSignal(Handle handle) {
/// Creates a TransferMemory object
static ResultCode CreateTransferMemory(Handle* handle, VAddr addr, u64 size, u32 permissions) {
- LOG_WARNING(Kernel_SVC, "(STUBBED) called addr=0x%llx, size=0x%llx, perms=%08X", addr, size,
- permissions);
+ NGLOG_WARNING(Kernel_SVC, "(STUBBED) called addr=0x{:X}, size=0x{:X}, perms=0x{:08X}", addr,
+ size, permissions);
*handle = 0;
return RESULT_SUCCESS;
}
-static ResultCode SetThreadCoreMask(u64, u64, u64) {
- LOG_WARNING(Kernel_SVC, "(STUBBED) called");
+static ResultCode GetThreadCoreMask(Handle thread_handle, u32* core, u64* mask) {
+ NGLOG_TRACE(Kernel_SVC, "called, handle=0x{:08X}", thread_handle);
+
+ const SharedPtr<Thread> thread = g_handle_table.Get<Thread>(thread_handle);
+ if (!thread) {
+ return ERR_INVALID_HANDLE;
+ }
+
+ *core = thread->ideal_core;
+ *mask = thread->affinity_mask;
+
+ return RESULT_SUCCESS;
+}
+
+static ResultCode SetThreadCoreMask(Handle thread_handle, u32 core, u64 mask) {
+ NGLOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, mask=0x{:16X}, core=0x{:X}", thread_handle,
+ mask, core);
+
+ const SharedPtr<Thread> thread = g_handle_table.Get<Thread>(thread_handle);
+ if (!thread) {
+ return ERR_INVALID_HANDLE;
+ }
+
+ if (core == THREADPROCESSORID_DEFAULT) {
+ ASSERT(thread->owner_process->ideal_processor != THREADPROCESSORID_DEFAULT);
+ // Set the target CPU to the one specified in the process' exheader.
+ core = thread->owner_process->ideal_processor;
+ mask = 1 << core;
+ }
+
+ if (mask == 0) {
+ return ResultCode(ErrorModule::Kernel, ErrCodes::InvalidCombination);
+ }
+
+ /// 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 (core == OnlyChangeMask) {
+ core = thread->ideal_core;
+ } else if (core >= Core::NUM_CPU_CORES && core != -1) {
+ return ResultCode(ErrorModule::Kernel, ErrCodes::InvalidProcessorId);
+ }
+
+ // Error out if the input core isn't enabled in the input mask.
+ if (core < Core::NUM_CPU_CORES && (mask & (1 << core)) == 0) {
+ return ResultCode(ErrorModule::Kernel, ErrCodes::InvalidCombination);
+ }
+
+ thread->ChangeCore(core, mask);
+
return RESULT_SUCCESS;
}
static ResultCode CreateSharedMemory(Handle* handle, u64 size, u32 local_permissions,
u32 remote_permissions) {
- LOG_TRACE(Kernel_SVC, "called, size=0x%llx, localPerms=0x%08x, remotePerms=0x%08x", size,
- local_permissions, remote_permissions);
+ NGLOG_TRACE(Kernel_SVC, "called, size=0x{:X}, localPerms=0x{:08X}, remotePerms=0x{:08X}", size,
+ local_permissions, remote_permissions);
auto sharedMemHandle =
SharedMemory::Create(g_handle_table.Get<Process>(KernelHandle::CurrentProcess), size,
static_cast<MemoryPermission>(local_permissions),
@@ -778,7 +789,7 @@ static ResultCode CreateSharedMemory(Handle* handle, u64 size, u32 local_permiss
}
static ResultCode ClearEvent(Handle handle) {
- LOG_TRACE(Kernel_SVC, "called, event=0xX", handle);
+ NGLOG_TRACE(Kernel_SVC, "called, event=0x{:08X}", handle);
SharedPtr<Event> evt = g_handle_table.Get<Event>(handle);
if (evt == nullptr)
@@ -812,7 +823,7 @@ static const FunctionDef SVC_Table[] = {
{0x0B, SvcWrap<SleepThread>, "SleepThread"},
{0x0C, SvcWrap<GetThreadPriority>, "GetThreadPriority"},
{0x0D, SvcWrap<SetThreadPriority>, "SetThreadPriority"},
- {0x0E, nullptr, "GetThreadCoreMask"},
+ {0x0E, SvcWrap<GetThreadCoreMask>, "GetThreadCoreMask"},
{0x0F, SvcWrap<SetThreadCoreMask>, "SetThreadCoreMask"},
{0x10, SvcWrap<GetCurrentProcessorNumber>, "GetCurrentProcessorNumber"},
{0x11, nullptr, "SignalEvent"},
@@ -844,14 +855,14 @@ static const FunctionDef SVC_Table[] = {
{0x2B, nullptr, "FlushDataCache"},
{0x2C, nullptr, "MapPhysicalMemory"},
{0x2D, nullptr, "UnmapPhysicalMemory"},
- {0x2E, nullptr, "Unknown"},
+ {0x2E, nullptr, "GetNextThreadInfo"},
{0x2F, nullptr, "GetLastThreadInfo"},
{0x30, nullptr, "GetResourceLimitLimitValue"},
{0x31, nullptr, "GetResourceLimitCurrentValue"},
- {0x32, nullptr, "SetThreadActivity"},
- {0x33, nullptr, "GetThreadContext"},
- {0x34, nullptr, "Unknown"},
- {0x35, nullptr, "Unknown"},
+ {0x32, SvcWrap<SetThreadActivity>, "SetThreadActivity"},
+ {0x33, SvcWrap<GetThreadContext>, "GetThreadContext"},
+ {0x34, nullptr, "WaitForAddress"},
+ {0x35, nullptr, "SignalToAddress"},
{0x36, nullptr, "Unknown"},
{0x37, nullptr, "Unknown"},
{0x38, nullptr, "Unknown"},
@@ -859,7 +870,7 @@ static const FunctionDef SVC_Table[] = {
{0x3A, nullptr, "Unknown"},
{0x3B, nullptr, "Unknown"},
{0x3C, nullptr, "DumpInfo"},
- {0x3D, nullptr, "Unknown"},
+ {0x3D, nullptr, "DumpInfoNew"},
{0x3E, nullptr, "Unknown"},
{0x3F, nullptr, "Unknown"},
{0x40, nullptr, "CreateSession"},
@@ -870,9 +881,9 @@ static const FunctionDef SVC_Table[] = {
{0x45, nullptr, "CreateEvent"},
{0x46, nullptr, "Unknown"},
{0x47, nullptr, "Unknown"},
- {0x48, nullptr, "Unknown"},
- {0x49, nullptr, "Unknown"},
- {0x4A, nullptr, "Unknown"},
+ {0x48, nullptr, "AllocateUnsafeMemory"},
+ {0x49, nullptr, "FreeUnsafeMemory"},
+ {0x4A, nullptr, "SetUnsafeAllocationLimit"},
{0x4B, nullptr, "CreateJitMemory"},
{0x4C, nullptr, "MapJitMemory"},
{0x4D, nullptr, "SleepSystem"},
@@ -909,7 +920,7 @@ static const FunctionDef SVC_Table[] = {
{0x6C, nullptr, "SetHardwareBreakPoint"},
{0x6D, nullptr, "GetDebugThreadParam"},
{0x6E, nullptr, "Unknown"},
- {0x6F, nullptr, "Unknown"},
+ {0x6F, nullptr, "GetMemoryInfo"},
{0x70, nullptr, "CreatePort"},
{0x71, nullptr, "ManageNamedPort"},
{0x72, nullptr, "ConnectToPort"},
@@ -929,8 +940,8 @@ static const FunctionDef SVC_Table[] = {
};
static const FunctionDef* GetSVCInfo(u32 func_num) {
- if (func_num >= ARRAY_SIZE(SVC_Table)) {
- LOG_ERROR(Kernel_SVC, "unknown svc=0x%02X", func_num);
+ if (func_num >= std::size(SVC_Table)) {
+ NGLOG_ERROR(Kernel_SVC, "Unknown svc=0x{:02X}", func_num);
return nullptr;
}
return &SVC_Table[func_num];
@@ -949,10 +960,10 @@ void CallSVC(u32 immediate) {
if (info->func) {
info->func();
} else {
- LOG_CRITICAL(Kernel_SVC, "unimplemented SVC function %s(..)", info->name);
+ NGLOG_CRITICAL(Kernel_SVC, "Unimplemented SVC function {}(..)", info->name);
}
} else {
- LOG_CRITICAL(Kernel_SVC, "unknown SVC function 0x%x", immediate);
+ NGLOG_CRITICAL(Kernel_SVC, "Unknown SVC function 0x{:X}", immediate);
}
}
diff --git a/src/core/hle/kernel/svc.h b/src/core/hle/kernel/svc.h
index bc471d01e..70148c4fe 100644
--- a/src/core/hle/kernel/svc.h
+++ b/src/core/hle/kernel/svc.h
@@ -47,9 +47,12 @@ enum class GetInfoType : u64 {
NewMapRegionSize = 15,
// 3.0.0+
IsVirtualAddressMemoryEnabled = 16,
+ PersonalMmHeapUsage = 17,
TitleId = 18,
// 4.0.0+
PrivilegedProcessId = 19,
+ // 5.0.0+
+ UserExceptionContextAddr = 20,
};
void CallSVC(u32 immediate);
diff --git a/src/core/hle/kernel/svc_wrap.h b/src/core/hle/kernel/svc_wrap.h
index b224f5e67..40aa88cc1 100644
--- a/src/core/hle/kernel/svc_wrap.h
+++ b/src/core/hle/kernel/svc_wrap.h
@@ -13,14 +13,14 @@
namespace Kernel {
-#define PARAM(n) Core::CPU().GetReg(n)
+#define PARAM(n) Core::CurrentArmInterface().GetReg(n)
/**
* HLE a function return from the current ARM userland process
* @param res Result to return
*/
static inline void FuncReturn(u64 res) {
- Core::CPU().SetReg(0, res);
+ Core::CurrentArmInterface().SetReg(0, res);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -45,7 +45,7 @@ template <ResultCode func(u32*, u32)>
void SvcWrap() {
u32 param_1 = 0;
u32 retval = func(&param_1, (u32)PARAM(1)).raw;
- Core::CPU().SetReg(1, param_1);
+ Core::CurrentArmInterface().SetReg(1, param_1);
FuncReturn(retval);
}
@@ -53,7 +53,7 @@ template <ResultCode func(u32*, u64)>
void SvcWrap() {
u32 param_1 = 0;
u32 retval = func(&param_1, PARAM(1)).raw;
- Core::CPU().SetReg(1, param_1);
+ Core::CurrentArmInterface().SetReg(1, param_1);
FuncReturn(retval);
}
@@ -66,10 +66,30 @@ template <ResultCode func(u64*, u64)>
void SvcWrap() {
u64 param_1 = 0;
u32 retval = func(&param_1, PARAM(1)).raw;
- Core::CPU().SetReg(1, param_1);
+ Core::CurrentArmInterface().SetReg(1, param_1);
FuncReturn(retval);
}
+template <ResultCode func(u32, u64)>
+void SvcWrap() {
+ FuncReturn(func((u32)(PARAM(0) & 0xFFFFFFFF), PARAM(1)).raw);
+}
+
+template <ResultCode func(u32, u32, u64)>
+void SvcWrap() {
+ FuncReturn(func((u32)(PARAM(0) & 0xFFFFFFFF), (u32)(PARAM(1) & 0xFFFFFFFF), PARAM(2)).raw);
+}
+
+template <ResultCode func(u32, u32*, u64*)>
+void SvcWrap() {
+ u32 param_1 = 0;
+ u64 param_2 = 0;
+ ResultCode retval = func((u32)(PARAM(2) & 0xFFFFFFFF), &param_1, &param_2);
+ Core::CurrentArmInterface().SetReg(1, param_1);
+ Core::CurrentArmInterface().SetReg(2, param_2);
+ FuncReturn(retval.raw);
+}
+
template <ResultCode func(u64, u64, u32, u32)>
void SvcWrap() {
FuncReturn(
@@ -100,7 +120,7 @@ template <ResultCode func(u32*, u64, u64, s64)>
void SvcWrap() {
u32 param_1 = 0;
ResultCode retval = func(&param_1, PARAM(1), (u32)(PARAM(2) & 0xFFFFFFFF), (s64)PARAM(3));
- Core::CPU().SetReg(1, param_1);
+ Core::CurrentArmInterface().SetReg(1, param_1);
FuncReturn(retval.raw);
}
@@ -113,7 +133,7 @@ template <ResultCode func(u64*, u64, u64, u64)>
void SvcWrap() {
u64 param_1 = 0;
u32 retval = func(&param_1, PARAM(1), PARAM(2), PARAM(3)).raw;
- Core::CPU().SetReg(1, param_1);
+ Core::CurrentArmInterface().SetReg(1, param_1);
FuncReturn(retval);
}
@@ -123,7 +143,7 @@ void SvcWrap() {
u32 retval =
func(&param_1, PARAM(1), PARAM(2), PARAM(3), (u32)PARAM(4), (s32)(PARAM(5) & 0xFFFFFFFF))
.raw;
- Core::CPU().SetReg(1, param_1);
+ Core::CurrentArmInterface().SetReg(1, param_1);
FuncReturn(retval);
}
@@ -146,7 +166,7 @@ template <ResultCode func(u32*, u64, u64, u32)>
void SvcWrap() {
u32 param_1 = 0;
u32 retval = func(&param_1, PARAM(1), PARAM(2), (u32)(PARAM(3) & 0xFFFFFFFF)).raw;
- Core::CPU().SetReg(1, param_1);
+ Core::CurrentArmInterface().SetReg(1, param_1);
FuncReturn(retval);
}
@@ -155,7 +175,7 @@ void SvcWrap() {
u32 param_1 = 0;
u32 retval =
func(&param_1, PARAM(1), (u32)(PARAM(2) & 0xFFFFFFFF), (u32)(PARAM(3) & 0xFFFFFFFF)).raw;
- Core::CPU().SetReg(1, param_1);
+ Core::CurrentArmInterface().SetReg(1, param_1);
FuncReturn(retval);
}
diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp
index dd0a8ae48..cffa7ca83 100644
--- a/src/core/hle/kernel/thread.cpp
+++ b/src/core/hle/kernel/thread.cpp
@@ -55,16 +55,6 @@ inline static u32 const NewThreadId() {
Thread::Thread() {}
Thread::~Thread() {}
-/**
- * Check if the specified thread is waiting on the specified address to be arbitrated
- * @param thread The thread to test
- * @param wait_address The address to test against
- * @return True if the thread is waiting, false otherwise
- */
-static bool CheckWait_AddressArbiter(const Thread* thread, VAddr wait_address) {
- return thread->status == THREADSTATUS_WAIT_ARB && wait_address == thread->wait_address;
-}
-
void Thread::Stop() {
// Cancel any outstanding wakeup events for this thread
CoreTiming::UnscheduleEvent(ThreadWakeupEventType, callback_handle);
@@ -74,7 +64,7 @@ void Thread::Stop() {
// Clean up thread from ready queue
// This is only needed when the thread is termintated forcefully (SVC TerminateProcess)
if (status == THREADSTATUS_READY) {
- Core::System::GetInstance().Scheduler().UnscheduleThread(this, current_priority);
+ scheduler->UnscheduleThread(this, current_priority);
}
status = THREADSTATUS_DEAD;
@@ -87,14 +77,11 @@ void Thread::Stop() {
}
wait_objects.clear();
- // Release all the mutexes that this thread holds
- ReleaseThreadMutexes(this);
-
// Mark the TLS slot in the thread's page as free.
u64 tls_page = (tls_address - Memory::TLS_AREA_VADDR) / Memory::PAGE_SIZE;
u64 tls_slot =
((tls_address - Memory::TLS_AREA_VADDR) % Memory::PAGE_SIZE) / Memory::TLS_ENTRY_SIZE;
- Kernel::g_current_process->tls_slots[tls_page].reset(tls_slot);
+ Core::CurrentProcess()->tls_slots[tls_page].reset(tls_slot);
}
void WaitCurrentThread_Sleep() {
@@ -102,16 +89,10 @@ void WaitCurrentThread_Sleep() {
thread->status = THREADSTATUS_WAIT_SLEEP;
}
-void WaitCurrentThread_ArbitrateAddress(VAddr wait_address) {
- Thread* thread = GetCurrentThread();
- thread->wait_address = wait_address;
- thread->status = THREADSTATUS_WAIT_ARB;
-}
-
void ExitCurrentThread() {
Thread* thread = GetCurrentThread();
thread->Stop();
- Core::System::GetInstance().Scheduler().RemoveThread(thread);
+ Core::System::GetInstance().CurrentScheduler().RemoveThread(thread);
}
/**
@@ -120,16 +101,18 @@ void ExitCurrentThread() {
* @param cycles_late The number of CPU cycles that have passed since the desired wakeup time
*/
static void ThreadWakeupCallback(u64 thread_handle, int cycles_late) {
- SharedPtr<Thread> thread = wakeup_callback_handle_table.Get<Thread>((Handle)thread_handle);
+ const auto proper_handle = static_cast<Handle>(thread_handle);
+ SharedPtr<Thread> thread = wakeup_callback_handle_table.Get<Thread>(proper_handle);
if (thread == nullptr) {
- LOG_CRITICAL(Kernel, "Callback fired for invalid thread %08X", (Handle)thread_handle);
+ NGLOG_CRITICAL(Kernel, "Callback fired for invalid thread {:08X}", proper_handle);
return;
}
bool resume = true;
if (thread->status == THREADSTATUS_WAIT_SYNCH_ANY ||
- thread->status == THREADSTATUS_WAIT_SYNCH_ALL || thread->status == THREADSTATUS_WAIT_ARB) {
+ thread->status == THREADSTATUS_WAIT_SYNCH_ALL ||
+ thread->status == THREADSTATUS_WAIT_HLE_EVENT) {
// Remove the thread from each of its waiting objects' waitlists
for (auto& object : thread->wait_objects)
@@ -141,6 +124,22 @@ static void ThreadWakeupCallback(u64 thread_handle, int cycles_late) {
resume = thread->wakeup_callback(ThreadWakeupReason::Timeout, thread, nullptr, 0);
}
+ if (thread->mutex_wait_address != 0 || thread->condvar_wait_address != 0 ||
+ thread->wait_handle) {
+ ASSERT(thread->status == THREADSTATUS_WAIT_MUTEX);
+ thread->mutex_wait_address = 0;
+ thread->condvar_wait_address = 0;
+ thread->wait_handle = 0;
+
+ auto lock_owner = thread->lock_owner;
+ // Threads waking up by timeout from WaitProcessWideKey do not perform priority inheritance
+ // and don't have a lock owner unless SignalProcessWideKey was called first and the thread
+ // wasn't awakened due to the mutex already being acquired.
+ if (lock_owner) {
+ lock_owner->RemoveMutexWaiter(thread);
+ }
+ }
+
if (resume)
thread->ResumeFromWait();
}
@@ -150,22 +149,36 @@ void Thread::WakeAfterDelay(s64 nanoseconds) {
if (nanoseconds == -1)
return;
- CoreTiming::ScheduleEvent(nsToCycles(nanoseconds), ThreadWakeupEventType, callback_handle);
+ CoreTiming::ScheduleEvent(CoreTiming::nsToCycles(nanoseconds), ThreadWakeupEventType,
+ callback_handle);
}
void Thread::CancelWakeupTimer() {
CoreTiming::UnscheduleEvent(ThreadWakeupEventType, callback_handle);
}
+static boost::optional<s32> GetNextProcessorId(u64 mask) {
+ for (s32 index = 0; index < Core::NUM_CPU_CORES; ++index) {
+ if (mask & (1ULL << index)) {
+ if (!Core::System().GetInstance().Scheduler(index)->GetCurrentThread()) {
+ // Core is enabled and not running any threads, use this one
+ return index;
+ }
+ }
+ }
+ return {};
+}
+
void Thread::ResumeFromWait() {
ASSERT_MSG(wait_objects.empty(), "Thread is waking up while waiting for objects");
switch (status) {
case THREADSTATUS_WAIT_SYNCH_ALL:
case THREADSTATUS_WAIT_SYNCH_ANY:
- case THREADSTATUS_WAIT_ARB:
+ case THREADSTATUS_WAIT_HLE_EVENT:
case THREADSTATUS_WAIT_SLEEP:
case THREADSTATUS_WAIT_IPC:
+ case THREADSTATUS_WAIT_MUTEX:
break;
case THREADSTATUS_READY:
@@ -178,11 +191,11 @@ void Thread::ResumeFromWait() {
return;
case THREADSTATUS_RUNNING:
- DEBUG_ASSERT_MSG(false, "Thread with object id %u has already resumed.", GetObjectId());
+ DEBUG_ASSERT_MSG(false, "Thread with object id {} has already resumed.", GetObjectId());
return;
case THREADSTATUS_DEAD:
// This should never happen, as threads must complete before being stopped.
- DEBUG_ASSERT_MSG(false, "Thread with object id %u cannot be resumed because it's DEAD.",
+ DEBUG_ASSERT_MSG(false, "Thread with object id {} cannot be resumed because it's DEAD.",
GetObjectId());
return;
}
@@ -190,8 +203,37 @@ void Thread::ResumeFromWait() {
wakeup_callback = nullptr;
status = THREADSTATUS_READY;
- Core::System::GetInstance().Scheduler().ScheduleThread(this, current_priority);
- Core::System::GetInstance().PrepareReschedule();
+
+ boost::optional<s32> new_processor_id = GetNextProcessorId(affinity_mask);
+ if (!new_processor_id) {
+ new_processor_id = processor_id;
+ }
+ if (ideal_core != -1 &&
+ Core::System().GetInstance().Scheduler(ideal_core)->GetCurrentThread() == nullptr) {
+ new_processor_id = ideal_core;
+ }
+
+ ASSERT(*new_processor_id < 4);
+
+ // Add thread to new core's scheduler
+ auto& next_scheduler = Core::System().GetInstance().Scheduler(*new_processor_id);
+
+ if (*new_processor_id != processor_id) {
+ // Remove thread from previous core's scheduler
+ scheduler->RemoveThread(this);
+ next_scheduler->AddThread(this, current_priority);
+ }
+
+ processor_id = *new_processor_id;
+
+ // If the thread was ready, unschedule from the previous core and schedule on the new core
+ scheduler->UnscheduleThread(this, current_priority);
+ next_scheduler->ScheduleThread(this, current_priority);
+
+ // Change thread's scheduler
+ scheduler = next_scheduler;
+
+ Core::System::GetInstance().CpuCore(processor_id).PrepareReschedule();
}
/**
@@ -242,27 +284,25 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point,
SharedPtr<Process> owner_process) {
// Check if priority is in ranged. Lowest priority -> highest priority id.
if (priority > THREADPRIO_LOWEST) {
- LOG_ERROR(Kernel_SVC, "Invalid thread priority: %u", priority);
+ NGLOG_ERROR(Kernel_SVC, "Invalid thread priority: {}", priority);
return ERR_OUT_OF_RANGE;
}
if (processor_id > THREADPROCESSORID_MAX) {
- LOG_ERROR(Kernel_SVC, "Invalid processor id: %d", processor_id);
+ NGLOG_ERROR(Kernel_SVC, "Invalid processor id: {}", processor_id);
return ERR_OUT_OF_RANGE_KERNEL;
}
// TODO(yuriks): Other checks, returning 0xD9001BEA
if (!Memory::IsValidVirtualAddress(*owner_process, entry_point)) {
- LOG_ERROR(Kernel_SVC, "(name=%s): invalid entry %016" PRIx64, name.c_str(), entry_point);
+ NGLOG_ERROR(Kernel_SVC, "(name={}): invalid entry {:016X}", name, entry_point);
// TODO (bunnei): Find the correct error code to use here
return ResultCode(-1);
}
SharedPtr<Thread> thread(new Thread);
- Core::System::GetInstance().Scheduler().AddThread(thread, priority);
-
thread->thread_id = NewThreadId();
thread->status = THREADSTATUS_DORMANT;
thread->entry_point = entry_point;
@@ -270,11 +310,17 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point,
thread->nominal_priority = thread->current_priority = priority;
thread->last_running_ticks = CoreTiming::GetTicks();
thread->processor_id = processor_id;
+ thread->ideal_core = processor_id;
+ thread->affinity_mask = 1ULL << processor_id;
thread->wait_objects.clear();
- thread->wait_address = 0;
+ thread->mutex_wait_address = 0;
+ thread->condvar_wait_address = 0;
+ thread->wait_handle = 0;
thread->name = std::move(name);
thread->callback_handle = wakeup_callback_handle_table.Create(thread).Unwrap();
thread->owner_process = owner_process;
+ thread->scheduler = Core::System().GetInstance().Scheduler(processor_id);
+ thread->scheduler->AddThread(thread, priority);
// Find the next available TLS index, and mark it as used
auto& tls_slots = owner_process->tls_slots;
@@ -291,8 +337,8 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point,
auto& linheap_memory = memory_region->linear_heap_memory;
if (linheap_memory->size() + Memory::PAGE_SIZE > memory_region->size) {
- LOG_ERROR(Kernel_SVC,
- "Not enough space in region to allocate a new TLS page for thread");
+ NGLOG_ERROR(Kernel_SVC,
+ "Not enough space in region to allocate a new TLS page for thread");
return ERR_OUT_OF_MEMORY;
}
@@ -314,7 +360,7 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point,
// TODO(Subv): Find the correct MemoryState for this region.
vm_manager.MapMemoryBlock(Memory::TLS_AREA_VADDR + available_page * Memory::PAGE_SIZE,
linheap_memory, offset, Memory::PAGE_SIZE,
- MemoryState::ThreadLocalStorage);
+ MemoryState::ThreadLocal);
}
// Mark the slot as used
@@ -332,32 +378,23 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point,
void Thread::SetPriority(u32 priority) {
ASSERT_MSG(priority <= THREADPRIO_LOWEST && priority >= THREADPRIO_HIGHEST,
"Invalid priority value.");
- Core::System::GetInstance().Scheduler().SetThreadPriority(this, priority);
- nominal_priority = current_priority = priority;
-}
-
-void Thread::UpdatePriority() {
- u32 best_priority = nominal_priority;
- for (auto& mutex : held_mutexes) {
- if (mutex->priority < best_priority)
- best_priority = mutex->priority;
- }
- BoostPriority(best_priority);
+ nominal_priority = priority;
+ UpdatePriority();
}
void Thread::BoostPriority(u32 priority) {
- Core::System::GetInstance().Scheduler().SetThreadPriority(this, priority);
+ scheduler->SetThreadPriority(this, priority);
current_priority = priority;
}
SharedPtr<Thread> SetupMainThread(VAddr entry_point, u32 priority,
SharedPtr<Process> owner_process) {
// Setup page table so we can write to memory
- SetCurrentPageTable(&Kernel::g_current_process->vm_manager.page_table);
+ SetCurrentPageTable(&Core::CurrentProcess()->vm_manager.page_table);
// Initialize new "main" thread
auto thread_res = Thread::Create("main", entry_point, priority, 0, THREADPROCESSORID_0,
- Memory::HEAP_VADDR_END, owner_process);
+ Memory::STACK_AREA_VADDR_END, owner_process);
SharedPtr<Thread> thread = std::move(thread_res).Unwrap();
@@ -392,13 +429,86 @@ VAddr Thread::GetCommandBufferAddress() const {
return GetTLSAddress() + CommandHeaderOffset;
}
+void Thread::AddMutexWaiter(SharedPtr<Thread> thread) {
+ thread->lock_owner = this;
+ wait_mutex_threads.emplace_back(std::move(thread));
+ UpdatePriority();
+}
+
+void Thread::RemoveMutexWaiter(SharedPtr<Thread> thread) {
+ 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
+ u32 new_priority = nominal_priority;
+ for (const auto& thread : wait_mutex_threads) {
+ if (thread->nominal_priority < new_priority)
+ new_priority = thread->nominal_priority;
+ }
+
+ if (new_priority == current_priority)
+ return;
+
+ scheduler->SetThreadPriority(this, new_priority);
+
+ current_priority = new_priority;
+
+ // Recursively update the priority of the thread that depends on the priority of this one.
+ if (lock_owner)
+ lock_owner->UpdatePriority();
+}
+
+void Thread::ChangeCore(u32 core, u64 mask) {
+ ideal_core = core;
+ affinity_mask = mask;
+
+ if (status != THREADSTATUS_READY) {
+ return;
+ }
+
+ boost::optional<s32> new_processor_id{GetNextProcessorId(affinity_mask)};
+
+ if (!new_processor_id) {
+ new_processor_id = processor_id;
+ }
+ if (ideal_core != -1 &&
+ Core::System().GetInstance().Scheduler(ideal_core)->GetCurrentThread() == nullptr) {
+ new_processor_id = ideal_core;
+ }
+
+ ASSERT(*new_processor_id < 4);
+
+ // Add thread to new core's scheduler
+ auto& next_scheduler = Core::System().GetInstance().Scheduler(*new_processor_id);
+
+ if (*new_processor_id != processor_id) {
+ // Remove thread from previous core's scheduler
+ scheduler->RemoveThread(this);
+ next_scheduler->AddThread(this, current_priority);
+ }
+
+ processor_id = *new_processor_id;
+
+ // If the thread was ready, unschedule from the previous core and schedule on the new core
+ scheduler->UnscheduleThread(this, current_priority);
+ next_scheduler->ScheduleThread(this, current_priority);
+
+ // Change thread's scheduler
+ scheduler = next_scheduler;
+
+ Core::System::GetInstance().CpuCore(processor_id).PrepareReschedule();
+}
+
////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Gets the current thread
*/
Thread* GetCurrentThread() {
- return Core::System::GetInstance().Scheduler().GetCurrentThread();
+ return Core::System::GetInstance().CurrentScheduler().GetCurrentThread();
}
void ThreadingInit() {
@@ -406,6 +516,8 @@ void ThreadingInit() {
next_thread_id = 1;
}
-void ThreadingShutdown() {}
+void ThreadingShutdown() {
+ Kernel::ClearProcessList();
+}
} // namespace Kernel
diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h
index 4fd2fc2f8..1d2da6d50 100644
--- a/src/core/hle/kernel/thread.h
+++ b/src/core/hle/kernel/thread.h
@@ -4,6 +4,7 @@
#pragma once
+#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
@@ -18,7 +19,7 @@
enum ThreadPriority : u32 {
THREADPRIO_HIGHEST = 0, ///< Highest thread priority
THREADPRIO_USERLAND_MAX = 24, ///< Highest thread priority for userland apps
- THREADPRIO_DEFAULT = 48, ///< Default thread priority for userland apps
+ THREADPRIO_DEFAULT = 44, ///< Default thread priority for userland apps
THREADPRIO_LOWEST = 63, ///< Lowest thread priority
};
@@ -38,11 +39,12 @@ enum ThreadProcessorId : s32 {
enum ThreadStatus {
THREADSTATUS_RUNNING, ///< Currently running
THREADSTATUS_READY, ///< Ready to run
- THREADSTATUS_WAIT_ARB, ///< Waiting on an address arbiter
+ THREADSTATUS_WAIT_HLE_EVENT, ///< Waiting for hle event to finish
THREADSTATUS_WAIT_SLEEP, ///< Waiting due to a SleepThread SVC
THREADSTATUS_WAIT_IPC, ///< Waiting for the reply from an IPC request
THREADSTATUS_WAIT_SYNCH_ANY, ///< Waiting due to WaitSynch1 or WaitSynchN with wait_all = false
THREADSTATUS_WAIT_SYNCH_ALL, ///< Waiting due to WaitSynchronizationN with wait_all = true
+ THREADSTATUS_WAIT_MUTEX, ///< Waiting due to an ArbitrateLock/WaitProcessWideKey svc
THREADSTATUS_DORMANT, ///< Created but not yet made ready
THREADSTATUS_DEAD ///< Run to completion, or forcefully terminated
};
@@ -54,8 +56,8 @@ enum class ThreadWakeupReason {
namespace Kernel {
-class Mutex;
class Process;
+class Scheduler;
class Thread final : public WaitObject {
public:
@@ -104,17 +106,23 @@ public:
void SetPriority(u32 priority);
/**
- * Boost's a thread's priority to the best priority among the thread's held mutexes.
- * This prevents priority inversion via priority inheritance.
- */
- void UpdatePriority();
-
- /**
* 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);
+
+ /// Removes a thread from the list of threads that are waiting for a lock held by this thread.
+ void RemoveMutexWaiter(SharedPtr<Thread> thread);
+
+ /// Recalculates the current priority taking into account priority inheritance.
+ void UpdatePriority();
+
+ /// Changes the core that the thread is running or scheduled to run on.
+ void ChangeCore(u32 core, u64 mask);
+
/**
* Gets the thread's thread ID
* @return The thread's ID
@@ -205,19 +213,22 @@ public:
VAddr tls_address; ///< Virtual address of the Thread Local Storage of the thread
- /// Mutexes currently held by this thread, which will be released when it exits.
- boost::container::flat_set<SharedPtr<Mutex>> held_mutexes;
-
- /// Mutexes that this thread is currently waiting for.
- boost::container::flat_set<SharedPtr<Mutex>> pending_mutexes;
-
SharedPtr<Process> owner_process; ///< Process that owns this thread
/// Objects that the thread is waiting on, in the same order as they were
// passed to WaitSynchronization1/N.
std::vector<SharedPtr<WaitObject>> wait_objects;
- VAddr wait_address; ///< If waiting on an AddressArbiter, this is the arbitration address
+ /// List of threads that are waiting for a mutex that is held by this thread.
+ std::vector<SharedPtr<Thread>> wait_mutex_threads;
+
+ /// Thread that owns the lock that this thread is waiting for.
+ SharedPtr<Thread> lock_owner;
+
+ // If waiting on a ConditionVariable, this is the ConditionVariable address
+ VAddr condvar_wait_address;
+ VAddr mutex_wait_address; ///< If waiting on a Mutex, this is the mutex address
+ Handle wait_handle; ///< The handle used to wait for the mutex.
std::string name;
@@ -234,6 +245,11 @@ public:
// available. In case of a timeout, the object will be nullptr.
std::function<WakeupCallback> wakeup_callback;
+ std::shared_ptr<Scheduler> scheduler;
+
+ u32 ideal_core{0xFFFFFFFF};
+ u64 affinity_mask{0x1};
+
private:
Thread();
~Thread() override;
diff --git a/src/core/hle/kernel/timer.cpp b/src/core/hle/kernel/timer.cpp
index 8da745634..661356a97 100644
--- a/src/core/hle/kernel/timer.cpp
+++ b/src/core/hle/kernel/timer.cpp
@@ -57,7 +57,8 @@ void Timer::Set(s64 initial, s64 interval) {
// Immediately invoke the callback
Signal(0);
} else {
- CoreTiming::ScheduleEvent(nsToCycles(initial), timer_callback_event_type, callback_handle);
+ CoreTiming::ScheduleEvent(CoreTiming::nsToCycles(initial), timer_callback_event_type,
+ callback_handle);
}
}
@@ -77,7 +78,7 @@ void Timer::WakeupAllWaitingThreads() {
}
void Timer::Signal(int cycles_late) {
- LOG_TRACE(Kernel, "Timer %u fired", GetObjectId());
+ NGLOG_TRACE(Kernel, "Timer {} fired", GetObjectId());
signaled = true;
@@ -86,7 +87,7 @@ void Timer::Signal(int cycles_late) {
if (interval_delay != 0) {
// Reschedule the timer with the interval delay
- CoreTiming::ScheduleEvent(nsToCycles(interval_delay) - cycles_late,
+ CoreTiming::ScheduleEvent(CoreTiming::nsToCycles(interval_delay) - cycles_late,
timer_callback_event_type, callback_handle);
}
}
@@ -97,7 +98,7 @@ static void TimerCallback(u64 timer_handle, int cycles_late) {
timer_callback_handle_table.Get<Timer>(static_cast<Handle>(timer_handle));
if (timer == nullptr) {
- LOG_CRITICAL(Kernel, "Callback fired for invalid timer %08" PRIx64, timer_handle);
+ NGLOG_CRITICAL(Kernel, "Callback fired for invalid timer {:016X}", timer_handle);
return;
}
diff --git a/src/core/hle/kernel/vm_manager.cpp b/src/core/hle/kernel/vm_manager.cpp
index d5b36d71a..676e5b282 100644
--- a/src/core/hle/kernel/vm_manager.cpp
+++ b/src/core/hle/kernel/vm_manager.cpp
@@ -2,7 +2,6 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include <cinttypes>
#include <iterator>
#include "common/assert.h"
#include "common/logging/log.h"
@@ -18,8 +17,26 @@ namespace Kernel {
static const char* GetMemoryStateName(MemoryState state) {
static const char* names[] = {
- "Free", "Reserved", "IO", "Static", "Code", "Private",
- "Shared", "Continuous", "Aliased", "Alias", "AliasCode", "Locked",
+ "Unmapped",
+ "Io",
+ "Normal",
+ "CodeStatic",
+ "CodeMutable",
+ "Heap",
+ "Shared",
+ "Unknown1"
+ "ModuleCodeStatic",
+ "ModuleCodeMutable",
+ "IpcBuffer0",
+ "Mapped",
+ "ThreadLocal",
+ "TransferMemoryIsolated",
+ "TransferMemory",
+ "ProcessMemory",
+ "Unknown2"
+ "IpcBuffer1",
+ "IpcBuffer3",
+ "KernelStack",
};
return names[(int)state];
@@ -87,8 +104,15 @@ ResultVal<VMManager::VMAHandle> VMManager::MapMemoryBlock(VAddr target,
VirtualMemoryArea& final_vma = vma_handle->second;
ASSERT(final_vma.size == size);
- Core::CPU().MapBackingMemory(target, size, block->data() + offset,
- VMAPermission::ReadWriteExecute);
+ 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,
+ VMAPermission::ReadWriteExecute);
+ system.ArmInterface(2).MapBackingMemory(target, size, block->data() + offset,
+ VMAPermission::ReadWriteExecute);
+ system.ArmInterface(3).MapBackingMemory(target, size, block->data() + offset,
+ VMAPermission::ReadWriteExecute);
final_vma.type = VMAType::AllocatedMemoryBlock;
final_vma.permissions = VMAPermission::ReadWrite;
@@ -109,7 +133,11 @@ ResultVal<VMManager::VMAHandle> VMManager::MapBackingMemory(VAddr target, u8* me
VirtualMemoryArea& final_vma = vma_handle->second;
ASSERT(final_vma.size == size);
- Core::CPU().MapBackingMemory(target, size, memory, VMAPermission::ReadWriteExecute);
+ 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);
+ system.ArmInterface(3).MapBackingMemory(target, size, memory, VMAPermission::ReadWriteExecute);
final_vma.type = VMAType::BackingMemory;
final_vma.permissions = VMAPermission::ReadWrite;
@@ -142,7 +170,7 @@ VMManager::VMAIter VMManager::Unmap(VMAIter vma_handle) {
VirtualMemoryArea& vma = vma_handle->second;
vma.type = VMAType::Free;
vma.permissions = VMAPermission::None;
- vma.meminfo_state = MemoryState::Free;
+ vma.meminfo_state = MemoryState::Unmapped;
vma.backing_block = nullptr;
vma.offset = 0;
@@ -166,6 +194,13 @@ 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);
+ system.ArmInterface(3).UnmapMemory(target, size);
+
return RESULT_SUCCESS;
}
@@ -204,11 +239,10 @@ void VMManager::RefreshMemoryBlockMappings(const std::vector<u8>* block) {
}
}
-void VMManager::LogLayout(Log::Level log_level) const {
+void VMManager::LogLayout() const {
for (const auto& p : vma_map) {
const VirtualMemoryArea& vma = p.second;
- LOG_GENERIC(Log::Class::Kernel, log_level,
- "%016" PRIx64 " - %016" PRIx64 " size: %16" PRIx64 " %c%c%c %s", vma.base,
+ NGLOG_DEBUG(Kernel, "{:016X} - {:016X} size: {:016X} {}{}{} {}", vma.base,
vma.base + vma.size, vma.size,
(u8)vma.permissions & (u8)VMAPermission::Read ? 'R' : '-',
(u8)vma.permissions & (u8)VMAPermission::Write ? 'W' : '-',
@@ -224,8 +258,8 @@ VMManager::VMAIter VMManager::StripIterConstness(const VMAHandle& iter) {
}
ResultVal<VMManager::VMAIter> VMManager::CarveVMA(VAddr base, u64 size) {
- ASSERT_MSG((size & Memory::PAGE_MASK) == 0, "non-page aligned size: 0x%16" PRIx64, size);
- ASSERT_MSG((base & Memory::PAGE_MASK) == 0, "non-page aligned base: 0x%016" PRIx64, base);
+ ASSERT_MSG((size & Memory::PAGE_MASK) == 0, "non-page aligned size: 0x{:016X}", size);
+ ASSERT_MSG((base & Memory::PAGE_MASK) == 0, "non-page aligned base: 0x{:016X}", base);
VMAIter vma_handle = StripIterConstness(FindVMA(base));
if (vma_handle == vma_map.end()) {
@@ -260,8 +294,8 @@ ResultVal<VMManager::VMAIter> VMManager::CarveVMA(VAddr base, u64 size) {
}
ResultVal<VMManager::VMAIter> VMManager::CarveVMARange(VAddr target, u64 size) {
- ASSERT_MSG((size & Memory::PAGE_MASK) == 0, "non-page aligned size: 0x%16" PRIx64, size);
- ASSERT_MSG((target & Memory::PAGE_MASK) == 0, "non-page aligned base: 0x%016" PRIx64, target);
+ ASSERT_MSG((size & Memory::PAGE_MASK) == 0, "non-page aligned size: 0x{:016X}", size);
+ ASSERT_MSG((target & Memory::PAGE_MASK) == 0, "non-page aligned base: 0x{:016X}", target);
VAddr target_end = target + size;
ASSERT(target_end >= target);
@@ -358,38 +392,23 @@ void VMManager::UpdatePageTableForVMA(const VirtualMemoryArea& vma) {
}
u64 VMManager::GetTotalMemoryUsage() {
- LOG_WARNING(Kernel, "(STUBBED) called");
- return 0xBE000000;
+ NGLOG_WARNING(Kernel, "(STUBBED) called");
+ return 0xF8000000;
}
u64 VMManager::GetTotalHeapUsage() {
- LOG_WARNING(Kernel, "(STUBBED) called");
+ NGLOG_WARNING(Kernel, "(STUBBED) called");
return 0x0;
}
VAddr VMManager::GetAddressSpaceBaseAddr() {
- LOG_WARNING(Kernel, "(STUBBED) called");
+ NGLOG_WARNING(Kernel, "(STUBBED) called");
return 0x8000000;
}
u64 VMManager::GetAddressSpaceSize() {
- LOG_WARNING(Kernel, "(STUBBED) called");
+ NGLOG_WARNING(Kernel, "(STUBBED) called");
return MAX_ADDRESS;
}
-VAddr VMManager::GetMapRegionBaseAddr() {
- LOG_WARNING(Kernel, "(STUBBED) called");
- return Memory::HEAP_VADDR;
-}
-
-VAddr VMManager::GetNewMapRegionBaseAddr() {
- LOG_WARNING(Kernel, "(STUBBED) called");
- return 0x8000000;
-}
-
-u64 VMManager::GetNewMapRegionSize() {
- LOG_WARNING(Kernel, "(STUBBED) called");
- return 0x8000000;
-}
-
} // namespace Kernel
diff --git a/src/core/hle/kernel/vm_manager.h b/src/core/hle/kernel/vm_manager.h
index 8de704a60..38e4ebcd3 100644
--- a/src/core/hle/kernel/vm_manager.h
+++ b/src/core/hle/kernel/vm_manager.h
@@ -41,15 +41,24 @@ enum class VMAPermission : u8 {
/// Set of values returned in MemoryInfo.state by svcQueryMemory.
enum class MemoryState : u32 {
- Free = 0,
- IO = 1,
- Normal = 2,
- Code = 3,
- Static = 4,
- Heap = 5,
- Shared = 6,
- Mapped = 6,
- ThreadLocalStorage = 12,
+ Unmapped = 0x0,
+ Io = 0x1,
+ Normal = 0x2,
+ CodeStatic = 0x3,
+ CodeMutable = 0x4,
+ Heap = 0x5,
+ Shared = 0x6,
+ ModuleCodeStatic = 0x8,
+ ModuleCodeMutable = 0x9,
+ IpcBuffer0 = 0xA,
+ Mapped = 0xB,
+ ThreadLocal = 0xC,
+ TransferMemoryIsolated = 0xD,
+ TransferMemory = 0xE,
+ ProcessMemory = 0xF,
+ IpcBuffer1 = 0x11,
+ IpcBuffer3 = 0x12,
+ KernelStack = 0x13,
};
/**
@@ -66,7 +75,7 @@ struct VirtualMemoryArea {
VMAType type = VMAType::Free;
VMAPermission permissions = VMAPermission::None;
/// Tag returned by svcQueryMemory. Not otherwise used.
- MemoryState meminfo_state = MemoryState::Free;
+ MemoryState meminfo_state = MemoryState::Unmapped;
// Settings for type = AllocatedMemoryBlock
/// Memory block backing this VMA.
@@ -178,7 +187,7 @@ public:
void RefreshMemoryBlockMappings(const std::vector<u8>* block);
/// Dumps the address space layout to the log, for debugging
- void LogLayout(Log::Level log_level) const;
+ void LogLayout() const;
/// Gets the total memory usage, used by svcGetInfo
u64 GetTotalMemoryUsage();
@@ -192,15 +201,6 @@ public:
/// Gets the total address space address size, used by svcGetInfo
u64 GetAddressSpaceSize();
- /// Gets the map region base address, used by svcGetInfo
- VAddr GetMapRegionBaseAddr();
-
- /// Gets the base address for a new memory region, used by svcGetInfo
- VAddr GetNewMapRegionBaseAddr();
-
- /// Gets the size for a new memory region, used by svcGetInfo
- u64 GetNewMapRegionSize();
-
/// Each VMManager has its own page table, which is set as the main one when the owning process
/// is scheduled.
Memory::PageTable page_table;
diff --git a/src/core/hle/kernel/wait_object.cpp b/src/core/hle/kernel/wait_object.cpp
index ec147b84c..b08ac72c1 100644
--- a/src/core/hle/kernel/wait_object.cpp
+++ b/src/core/hle/kernel/wait_object.cpp
@@ -39,7 +39,8 @@ SharedPtr<Thread> WaitObject::GetHighestPriorityReadyThread() {
for (const auto& thread : waiting_threads) {
// The list of waiting threads must not contain threads that are not waiting to be awakened.
ASSERT_MSG(thread->status == THREADSTATUS_WAIT_SYNCH_ANY ||
- thread->status == THREADSTATUS_WAIT_SYNCH_ALL,
+ thread->status == THREADSTATUS_WAIT_SYNCH_ALL ||
+ thread->status == THREADSTATUS_WAIT_HLE_EVENT,
"Inconsistent thread statuses in waiting_threads");
if (thread->current_priority >= candidate_priority)
diff --git a/src/core/hle/result.h b/src/core/hle/result.h
index 656e1b4a7..3ebf7aadf 100644
--- a/src/core/hle/result.h
+++ b/src/core/hle/result.h
@@ -32,7 +32,8 @@ enum class ErrorModule : u32 {
Common = 0,
Kernel = 1,
FS = 2,
- NvidiaTransferMemory = 3,
+ OS = 3, // used for Memory, Thread, Mutex, Nvidia
+ HTCS = 4,
NCM = 5,
DD = 6,
LR = 8,
@@ -42,41 +43,80 @@ enum class ErrorModule : u32 {
PM = 15,
NS = 16,
HTC = 18,
+ NCMContent = 20,
SM = 21,
RO = 22,
SDMMC = 24,
+ OVLN = 25,
SPL = 26,
ETHC = 100,
I2C = 101,
+ GPIO = 102,
+ UART = 103,
Settings = 105,
+ WLAN = 107,
+ XCD = 108,
NIFM = 110,
- Display = 114,
- NTC = 116,
+ Hwopus = 111,
+ Bluetooth = 113,
+ VI = 114,
+ NFP = 115,
+ Time = 116,
FGM = 117,
- PCIE = 120,
+ OE = 118,
+ PCIe = 120,
Friends = 121,
+ BCAT = 122,
SSL = 123,
Account = 124,
+ News = 125,
Mii = 126,
+ NFC = 127,
AM = 128,
PlayReport = 129,
+ AHID = 130,
+ Qlaunch = 132,
PCV = 133,
OMM = 134,
+ BPC = 135,
+ PSM = 136,
NIM = 137,
PSC = 138,
+ TC = 139,
USB = 140,
+ NSD = 141,
+ PCTL = 142,
BTM = 143,
+ ETicket = 145,
+ NGC = 146,
ERPT = 147,
APM = 148,
+ Profiler = 150,
+ ErrorUpload = 151,
+ Audio = 153,
NPNS = 154,
+ NPNSHTTPSTREAM = 155,
ARP = 157,
- BOOT = 158,
- NFC = 161,
+ SWKBD = 158,
+ BOOT = 159,
+ NFCMifare = 161,
UserlandAssert = 162,
+ Fatal = 163,
+ NIMShop = 164,
+ SPSM = 165,
+ BGTC = 167,
UserlandCrash = 168,
- HID = 203,
+ SREPO = 180,
+ Dauth = 181,
+ HID = 202,
+ LDN = 203,
+ Irsensor = 205,
Capture = 206,
- TC = 651,
+ Manu = 208,
+ ATK = 209,
+ GRC = 212,
+ Migration = 216,
+ MigrationLdcServ = 217,
GeneralWebApplet = 800,
WifiWebAuthApplet = 809,
WhitelistedApplet = 810,
@@ -108,11 +148,11 @@ union ResultCode {
}
constexpr bool IsSuccess() const {
- return is_error.ExtractValue(raw) == 0;
+ return raw == 0;
}
constexpr bool IsError() const {
- return is_error.ExtractValue(raw) == 1;
+ return raw != 0;
}
};
@@ -200,6 +240,9 @@ public:
}
ResultVal& operator=(const ResultVal& o) {
+ if (this == &o) {
+ return *this;
+ }
if (!empty()) {
if (!o.empty()) {
object = o.object;
diff --git a/src/core/hle/service/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp
index 5716577d6..f2fffa760 100644
--- a/src/core/hle/service/acc/acc.cpp
+++ b/src/core/hle/service/acc/acc.cpp
@@ -2,15 +2,149 @@
// 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/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"
-namespace Service {
-namespace Account {
+namespace Service::Account {
+
+// TODO: RE this structure
+struct UserData {
+ INSERT_PADDING_WORDS(1);
+ u32 icon_id;
+ u8 bg_color_id;
+ INSERT_PADDING_BYTES(0x7);
+ INSERT_PADDING_BYTES(0x10);
+ INSERT_PADDING_BYTES(0x60);
+};
+static_assert(sizeof(UserData) == 0x80, "UserData structure has incorrect size");
+
+struct ProfileBase {
+ u8 user_id[0x10];
+ u64 timestamp;
+ u8 username[0x20];
+};
+static_assert(sizeof(ProfileBase) == 0x38, "ProfileBase structure has incorrect size");
+
+using Uid = std::array<u64, 2>;
+static constexpr Uid DEFAULT_USER_ID{0x10ull, 0x20ull};
+
+class IProfile final : public ServiceFramework<IProfile> {
+public:
+ IProfile() : ServiceFramework("IProfile") {
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "Get"},
+ {1, &IProfile::GetBase, "GetBase"},
+ {10, nullptr, "GetImageSize"},
+ {11, nullptr, "LoadImage"},
+ };
+ RegisterHandlers(functions);
+ }
+
+private:
+ void GetBase(Kernel::HLERequestContext& ctx) {
+ NGLOG_WARNING(Service_ACC, "(STUBBED) called");
+ ProfileBase profile_base{};
+ IPC::ResponseBuilder rb{ctx, 16};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushRaw(profile_base);
+ }
+};
+
+class IManagerForApplication final : public ServiceFramework<IManagerForApplication> {
+public:
+ IManagerForApplication() : ServiceFramework("IManagerForApplication") {
+ static const FunctionInfo functions[] = {
+ {0, &IManagerForApplication::CheckAvailability, "CheckAvailability"},
+ {1, &IManagerForApplication::GetAccountId, "GetAccountId"},
+ {2, nullptr, "EnsureIdTokenCacheAsync"},
+ {3, nullptr, "LoadIdTokenCache"},
+ {130, nullptr, "GetNintendoAccountUserResourceCacheForApplication"},
+ {150, nullptr, "CreateAuthorizationRequest"},
+ {160, nullptr, "StoreOpenContext"},
+ };
+ RegisterHandlers(functions);
+ }
+
+private:
+ void CheckAvailability(Kernel::HLERequestContext& ctx) {
+ NGLOG_WARNING(Service_ACC, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(true); // TODO: Check when this is supposed to return true and when not
+ }
+
+ void GetAccountId(Kernel::HLERequestContext& ctx) {
+ NGLOG_WARNING(Service_ACC, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u64>(0x12345678ABCDEF);
+ }
+};
+
+void Module::Interface::GetUserExistence(Kernel::HLERequestContext& ctx) {
+ NGLOG_WARNING(Service_ACC, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(true); // TODO: Check when this is supposed to return true and when not
+}
+
+void Module::Interface::ListAllUsers(Kernel::HLERequestContext& ctx) {
+ NGLOG_WARNING(Service_ACC, "(STUBBED) called");
+ constexpr std::array<u128, 10> user_ids{DEFAULT_USER_ID};
+ ctx.WriteBuffer(user_ids.data(), user_ids.size());
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
+void Module::Interface::ListOpenUsers(Kernel::HLERequestContext& ctx) {
+ NGLOG_WARNING(Service_ACC, "(STUBBED) called");
+ constexpr std::array<u128, 10> user_ids{DEFAULT_USER_ID};
+ ctx.WriteBuffer(user_ids.data(), user_ids.size());
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
+void Module::Interface::GetProfile(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IProfile>();
+ NGLOG_DEBUG(Service_ACC, "called");
+}
+
+void Module::Interface::InitializeApplicationInfo(Kernel::HLERequestContext& ctx) {
+ NGLOG_WARNING(Service_ACC, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
+void Module::Interface::GetBaasAccountManagerForApplication(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IManagerForApplication>();
+ NGLOG_DEBUG(Service_ACC, "called");
+}
+
+void Module::Interface::GetLastOpenedUser(Kernel::HLERequestContext& ctx) {
+ NGLOG_WARNING(Service_ACC, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 6};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushRaw(DEFAULT_USER_ID);
+}
+
+Module::Interface::Interface(std::shared_ptr<Module> module, const char* name)
+ : ServiceFramework(name), module(std::move(module)) {}
void InstallInterfaces(SM::ServiceManager& service_manager) {
- std::make_shared<ACC_U0>()->InstallAsService(service_manager);
+ auto module = std::make_shared<Module>();
+ std::make_shared<ACC_AA>(module)->InstallAsService(service_manager);
+ std::make_shared<ACC_SU>(module)->InstallAsService(service_manager);
+ std::make_shared<ACC_U0>(module)->InstallAsService(service_manager);
+ std::make_shared<ACC_U1>(module)->InstallAsService(service_manager);
}
-} // namespace Account
-} // namespace Service
+} // namespace Service::Account
diff --git a/src/core/hle/service/acc/acc.h b/src/core/hle/service/acc/acc.h
index 44d024f48..58f8d260c 100644
--- a/src/core/hle/service/acc/acc.h
+++ b/src/core/hle/service/acc/acc.h
@@ -6,11 +6,28 @@
#include "core/hle/service/service.h"
-namespace Service {
-namespace Account {
+namespace Service::Account {
+
+class Module final {
+public:
+ class Interface : public ServiceFramework<Interface> {
+ public:
+ Interface(std::shared_ptr<Module> module, const char* name);
+
+ void GetUserExistence(Kernel::HLERequestContext& ctx);
+ void ListAllUsers(Kernel::HLERequestContext& ctx);
+ void ListOpenUsers(Kernel::HLERequestContext& ctx);
+ void GetLastOpenedUser(Kernel::HLERequestContext& ctx);
+ void GetProfile(Kernel::HLERequestContext& ctx);
+ void InitializeApplicationInfo(Kernel::HLERequestContext& ctx);
+ void GetBaasAccountManagerForApplication(Kernel::HLERequestContext& ctx);
+
+ protected:
+ std::shared_ptr<Module> module;
+ };
+};
/// Registers all ACC services with the specified service manager.
void InstallInterfaces(SM::ServiceManager& service_manager);
-} // namespace Account
-} // namespace Service
+} // namespace Service::Account
diff --git a/src/core/hle/service/acc/acc_aa.cpp b/src/core/hle/service/acc/acc_aa.cpp
new file mode 100644
index 000000000..280b3e464
--- /dev/null
+++ b/src/core/hle/service/acc/acc_aa.cpp
@@ -0,0 +1,20 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/acc/acc_aa.h"
+
+namespace Service::Account {
+
+ACC_AA::ACC_AA(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "acc:aa") {
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "EnsureCacheAsync"},
+ {1, nullptr, "LoadCache"},
+ {2, nullptr, "GetDeviceAccountId"},
+ {50, nullptr, "RegisterNotificationTokenAsync"},
+ {51, nullptr, "UnregisterNotificationTokenAsync"},
+ };
+ RegisterHandlers(functions);
+}
+
+} // namespace Service::Account
diff --git a/src/core/hle/service/acc/acc_aa.h b/src/core/hle/service/acc/acc_aa.h
new file mode 100644
index 000000000..796f7ef85
--- /dev/null
+++ b/src/core/hle/service/acc/acc_aa.h
@@ -0,0 +1,16 @@
+// 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/acc/acc.h"
+
+namespace Service::Account {
+
+class ACC_AA final : public Module::Interface {
+public:
+ explicit ACC_AA(std::shared_ptr<Module> module);
+};
+
+} // namespace Service::Account
diff --git a/src/core/hle/service/acc/acc_su.cpp b/src/core/hle/service/acc/acc_su.cpp
new file mode 100644
index 000000000..9ffb40b22
--- /dev/null
+++ b/src/core/hle/service/acc/acc_su.cpp
@@ -0,0 +1,53 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/acc/acc_su.h"
+
+namespace Service::Account {
+
+ACC_SU::ACC_SU(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "acc:su") {
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "GetUserCount"},
+ {1, &ACC_SU::GetUserExistence, "GetUserExistence"},
+ {2, &ACC_SU::ListAllUsers, "ListAllUsers"},
+ {3, &ACC_SU::ListOpenUsers, "ListOpenUsers"},
+ {4, &ACC_SU::GetLastOpenedUser, "GetLastOpenedUser"},
+ {5, &ACC_SU::GetProfile, "GetProfile"},
+ {6, nullptr, "GetProfileDigest"},
+ {50, nullptr, "IsUserRegistrationRequestPermitted"},
+ {51, nullptr, "TrySelectUserWithoutInteraction"},
+ {60, nullptr, "ListOpenContextStoredUsers"},
+ {100, nullptr, "GetUserRegistrationNotifier"},
+ {101, nullptr, "GetUserStateChangeNotifier"},
+ {102, nullptr, "GetBaasAccountManagerForSystemService"},
+ {103, nullptr, "GetBaasUserAvailabilityChangeNotifier"},
+ {104, nullptr, "GetProfileUpdateNotifier"},
+ {105, nullptr, "CheckNetworkServiceAvailabilityAsync"},
+ {110, nullptr, "StoreSaveDataThumbnail"},
+ {111, nullptr, "ClearSaveDataThumbnail"},
+ {112, nullptr, "LoadSaveDataThumbnail"},
+ {113, nullptr, "GetSaveDataThumbnailExistence"},
+ {190, nullptr, "GetUserLastOpenedApplication"},
+ {191, nullptr, "ActivateOpenContextHolder"},
+ {200, nullptr, "BeginUserRegistration"},
+ {201, nullptr, "CompleteUserRegistration"},
+ {202, nullptr, "CancelUserRegistration"},
+ {203, nullptr, "DeleteUser"},
+ {204, nullptr, "SetUserPosition"},
+ {205, nullptr, "GetProfileEditor"},
+ {206, nullptr, "CompleteUserRegistrationForcibly"},
+ {210, nullptr, "CreateFloatingRegistrationRequest"},
+ {230, nullptr, "AuthenticateServiceAsync"},
+ {250, nullptr, "GetBaasAccountAdministrator"},
+ {290, nullptr, "ProxyProcedureForGuestLoginWithNintendoAccount"},
+ {291, nullptr, "ProxyProcedureForFloatingRegistrationWithNintendoAccount"},
+ {299, nullptr, "SuspendBackgroundDaemon"},
+ {997, nullptr, "DebugInvalidateTokenCacheForUser"},
+ {998, nullptr, "DebugSetUserStateClose"},
+ {999, nullptr, "DebugSetUserStateOpen"},
+ };
+ RegisterHandlers(functions);
+}
+
+} // namespace Service::Account
diff --git a/src/core/hle/service/acc/acc_su.h b/src/core/hle/service/acc/acc_su.h
new file mode 100644
index 000000000..3894a6991
--- /dev/null
+++ b/src/core/hle/service/acc/acc_su.h
@@ -0,0 +1,18 @@
+// 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/acc/acc.h"
+
+namespace Service {
+namespace Account {
+
+class ACC_SU final : public Module::Interface {
+public:
+ explicit ACC_SU(std::shared_ptr<Module> module);
+};
+
+} // namespace Account
+} // namespace Service
diff --git a/src/core/hle/service/acc/acc_u0.cpp b/src/core/hle/service/acc/acc_u0.cpp
index 52c3491d5..44e21ac09 100644
--- a/src/core/hle/service/acc/acc_u0.cpp
+++ b/src/core/hle/service/acc/acc_u0.cpp
@@ -2,123 +2,32 @@
// 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/acc/acc_u0.h"
-namespace Service {
-namespace Account {
+namespace Service::Account {
-using Uid = std::array<u64, 2>;
-static constexpr Uid DEFAULT_USER_ID{0x10ull, 0x20ull};
-
-class IProfile final : public ServiceFramework<IProfile> {
-public:
- IProfile() : ServiceFramework("IProfile") {
- static const FunctionInfo functions[] = {
- {1, &IProfile::GetBase, "GetBase"},
- };
- RegisterHandlers(functions);
- }
-
-private:
- void GetBase(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_ACC, "(STUBBED) called");
- ProfileBase profile_base{};
- IPC::ResponseBuilder rb{ctx, 16};
- rb.Push(RESULT_SUCCESS);
- rb.PushRaw(profile_base);
- }
-};
-
-class IManagerForApplication final : public ServiceFramework<IManagerForApplication> {
-public:
- IManagerForApplication() : ServiceFramework("IManagerForApplication") {
- static const FunctionInfo functions[] = {
- {0, &IManagerForApplication::CheckAvailability, "CheckAvailability"},
- {1, &IManagerForApplication::GetAccountId, "GetAccountId"},
- };
- RegisterHandlers(functions);
- }
-
-private:
- void CheckAvailability(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_ACC, "(STUBBED) called");
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(RESULT_SUCCESS);
- rb.Push(true); // TODO: Check when this is supposed to return true and when not
- }
-
- void GetAccountId(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_ACC, "(STUBBED) called");
- IPC::ResponseBuilder rb{ctx, 4};
- rb.Push(RESULT_SUCCESS);
- rb.Push<u64>(0x12345678ABCDEF);
- }
-};
-
-void ACC_U0::GetUserExistence(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_ACC, "(STUBBED) called");
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(RESULT_SUCCESS);
- rb.Push(true); // TODO: Check when this is supposed to return true and when not
-}
-
-void ACC_U0::ListAllUsers(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_ACC, "(STUBBED) called");
- constexpr std::array<u128, 10> user_ids{DEFAULT_USER_ID};
- ctx.WriteBuffer(user_ids.data(), user_ids.size());
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
-}
-
-void ACC_U0::ListOpenUsers(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_ACC, "(STUBBED) called");
- constexpr std::array<u128, 10> user_ids{DEFAULT_USER_ID};
- ctx.WriteBuffer(user_ids.data(), user_ids.size());
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
-}
-
-void ACC_U0::GetProfile(Kernel::HLERequestContext& ctx) {
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IProfile>();
- LOG_DEBUG(Service_ACC, "called");
-}
-
-void ACC_U0::InitializeApplicationInfo(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_ACC, "(STUBBED) called");
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
-}
-
-void ACC_U0::GetBaasAccountManagerForApplication(Kernel::HLERequestContext& ctx) {
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IManagerForApplication>();
- LOG_DEBUG(Service_ACC, "called");
-}
-
-void ACC_U0::GetLastOpenedUser(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_ACC, "(STUBBED) called");
- IPC::ResponseBuilder rb{ctx, 6};
- rb.Push(RESULT_SUCCESS);
- rb.PushRaw(DEFAULT_USER_ID);
-}
-
-ACC_U0::ACC_U0() : ServiceFramework("acc:u0") {
+ACC_U0::ACC_U0(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "acc:u0") {
static const FunctionInfo functions[] = {
+ {0, nullptr, "GetUserCount"},
{1, &ACC_U0::GetUserExistence, "GetUserExistence"},
{2, &ACC_U0::ListAllUsers, "ListAllUsers"},
{3, &ACC_U0::ListOpenUsers, "ListOpenUsers"},
{4, &ACC_U0::GetLastOpenedUser, "GetLastOpenedUser"},
{5, &ACC_U0::GetProfile, "GetProfile"},
+ {6, nullptr, "GetProfileDigest"},
+ {50, nullptr, "IsUserRegistrationRequestPermitted"},
+ {51, nullptr, "TrySelectUserWithoutInteraction"},
+ {60, nullptr, "ListOpenContextStoredUsers"},
{100, &ACC_U0::InitializeApplicationInfo, "InitializeApplicationInfo"},
{101, &ACC_U0::GetBaasAccountManagerForApplication, "GetBaasAccountManagerForApplication"},
+ {102, nullptr, "AuthenticateApplicationAsync"},
+ {103, nullptr, "CheckNetworkServiceAvailabilityAsync"},
+ {110, nullptr, "StoreSaveDataThumbnail"},
+ {111, nullptr, "ClearSaveDataThumbnail"},
+ {120, nullptr, "CreateGuestLoginRequest"},
+ {130, nullptr, "LoadOpenContext"},
};
RegisterHandlers(functions);
}
-} // namespace Account
-} // namespace Service
+} // namespace Service::Account
diff --git a/src/core/hle/service/acc/acc_u0.h b/src/core/hle/service/acc/acc_u0.h
index 222f37282..6ded596b3 100644
--- a/src/core/hle/service/acc/acc_u0.h
+++ b/src/core/hle/service/acc/acc_u0.h
@@ -4,37 +4,13 @@
#pragma once
-#include "core/hle/service/service.h"
+#include "core/hle/service/acc/acc.h"
-namespace Service {
-namespace Account {
+namespace Service::Account {
-// TODO: RE this structure
-struct UserData {
- INSERT_PADDING_BYTES(0x80);
-};
-static_assert(sizeof(UserData) == 0x80, "UserData structure has incorrect size");
-
-// TODO: RE this structure
-struct ProfileBase {
- INSERT_PADDING_BYTES(0x38);
-};
-static_assert(sizeof(ProfileBase) == 0x38, "ProfileBase structure has incorrect size");
-
-class ACC_U0 final : public ServiceFramework<ACC_U0> {
+class ACC_U0 final : public Module::Interface {
public:
- ACC_U0();
- ~ACC_U0() = default;
-
-private:
- void GetUserExistence(Kernel::HLERequestContext& ctx);
- void ListAllUsers(Kernel::HLERequestContext& ctx);
- void ListOpenUsers(Kernel::HLERequestContext& ctx);
- void GetLastOpenedUser(Kernel::HLERequestContext& ctx);
- void GetProfile(Kernel::HLERequestContext& ctx);
- void InitializeApplicationInfo(Kernel::HLERequestContext& ctx);
- void GetBaasAccountManagerForApplication(Kernel::HLERequestContext& ctx);
+ explicit ACC_U0(std::shared_ptr<Module> module);
};
-} // namespace Account
-} // namespace Service
+} // namespace Service::Account
diff --git a/src/core/hle/service/acc/acc_u1.cpp b/src/core/hle/service/acc/acc_u1.cpp
new file mode 100644
index 000000000..d101d4e0d
--- /dev/null
+++ b/src/core/hle/service/acc/acc_u1.cpp
@@ -0,0 +1,40 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/acc/acc_u1.h"
+
+namespace Service::Account {
+
+ACC_U1::ACC_U1(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "acc:u1") {
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "GetUserCount"},
+ {1, &ACC_U1::GetUserExistence, "GetUserExistence"},
+ {2, &ACC_U1::ListAllUsers, "ListAllUsers"},
+ {3, &ACC_U1::ListOpenUsers, "ListOpenUsers"},
+ {4, &ACC_U1::GetLastOpenedUser, "GetLastOpenedUser"},
+ {5, &ACC_U1::GetProfile, "GetProfile"},
+ {6, nullptr, "GetProfileDigest"},
+ {50, nullptr, "IsUserRegistrationRequestPermitted"},
+ {51, nullptr, "TrySelectUserWithoutInteraction"},
+ {60, nullptr, "ListOpenContextStoredUsers"},
+ {100, nullptr, "GetUserRegistrationNotifier"},
+ {101, nullptr, "GetUserStateChangeNotifier"},
+ {102, nullptr, "GetBaasAccountManagerForSystemService"},
+ {103, nullptr, "GetProfileUpdateNotifier"},
+ {104, nullptr, "CheckNetworkServiceAvailabilityAsync"},
+ {105, nullptr, "GetBaasUserAvailabilityChangeNotifier"},
+ {110, nullptr, "StoreSaveDataThumbnail"},
+ {111, nullptr, "ClearSaveDataThumbnail"},
+ {112, nullptr, "LoadSaveDataThumbnail"},
+ {113, nullptr, "GetSaveDataThumbnailExistence"},
+ {190, nullptr, "GetUserLastOpenedApplication"},
+ {191, nullptr, "ActivateOpenContextHolder"},
+ {997, nullptr, "DebugInvalidateTokenCacheForUser"},
+ {998, nullptr, "DebugSetUserStateClose"},
+ {999, nullptr, "DebugSetUserStateOpen"},
+ };
+ RegisterHandlers(functions);
+}
+
+} // namespace Service::Account
diff --git a/src/core/hle/service/acc/acc_u1.h b/src/core/hle/service/acc/acc_u1.h
new file mode 100644
index 000000000..5e3e7659b
--- /dev/null
+++ b/src/core/hle/service/acc/acc_u1.h
@@ -0,0 +1,16 @@
+// 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/acc/acc.h"
+
+namespace Service::Account {
+
+class ACC_U1 final : public Module::Interface {
+public:
+ explicit ACC_U1(std::shared_ptr<Module> module);
+};
+
+} // namespace Service::Account
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index d3a674cf6..b8d6b8d4d 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -2,34 +2,42 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <cinttypes>
+#include <stack>
+#include "core/file_sys/filesystem.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/event.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applet_ae.h"
#include "core/hle/service/am/applet_oe.h"
#include "core/hle/service/apm/apm.h"
+#include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/nvflinger/nvflinger.h"
+#include "core/hle/service/set/set.h"
+#include "core/settings.h"
-namespace Service {
-namespace AM {
+namespace Service::AM {
IWindowController::IWindowController() : ServiceFramework("IWindowController") {
static const FunctionInfo functions[] = {
+ {0, nullptr, "CreateWindow"},
{1, &IWindowController::GetAppletResourceUserId, "GetAppletResourceUserId"},
{10, &IWindowController::AcquireForegroundRights, "AcquireForegroundRights"},
+ {11, nullptr, "ReleaseForegroundRights"},
+ {12, nullptr, "RejectToChangeIntoBackground"},
};
RegisterHandlers(functions);
}
void IWindowController::GetAppletResourceUserId(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_AM, "(STUBBED) called");
+ NGLOG_WARNING(Service_AM, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
rb.Push<u64>(0);
}
void IWindowController::AcquireForegroundRights(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_AM, "(STUBBED) called");
+ NGLOG_WARNING(Service_AM, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
@@ -48,34 +56,70 @@ IAudioController::IAudioController() : ServiceFramework("IAudioController") {
}
void IAudioController::SetExpectedMasterVolume(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_AM, "(STUBBED) called");
+ NGLOG_WARNING(Service_AM, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void IAudioController::GetMainAppletExpectedMasterVolume(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_AM, "(STUBBED) called");
+ NGLOG_WARNING(Service_AM, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push(volume);
}
void IAudioController::GetLibraryAppletExpectedMasterVolume(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_AM, "(STUBBED) called");
+ NGLOG_WARNING(Service_AM, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push(volume);
}
-IDisplayController::IDisplayController() : ServiceFramework("IDisplayController") {}
+IDisplayController::IDisplayController() : ServiceFramework("IDisplayController") {
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "GetLastForegroundCaptureImage"},
+ {1, nullptr, "UpdateLastForegroundCaptureImage"},
+ {2, nullptr, "GetLastApplicationCaptureImage"},
+ {3, nullptr, "GetCallerAppletCaptureImage"},
+ {4, nullptr, "UpdateCallerAppletCaptureImage"},
+ {5, nullptr, "GetLastForegroundCaptureImageEx"},
+ {6, nullptr, "GetLastApplicationCaptureImageEx"},
+ {7, nullptr, "GetCallerAppletCaptureImageEx"},
+ {8, nullptr, "TakeScreenShotOfOwnLayer"}, // 2.0.0+
+ {9, nullptr, "CopyBetweenCaptureBuffers"}, // 5.0.0+
+ {10, nullptr, "AcquireLastApplicationCaptureBuffer"},
+ {11, nullptr, "ReleaseLastApplicationCaptureBuffer"},
+ {12, nullptr, "AcquireLastForegroundCaptureBuffer"},
+ {13, nullptr, "ReleaseLastForegroundCaptureBuffer"},
+ {14, nullptr, "AcquireCallerAppletCaptureBuffer"},
+ {15, nullptr, "ReleaseCallerAppletCaptureBuffer"},
+ {16, nullptr, "AcquireLastApplicationCaptureBufferEx"},
+ {17, nullptr, "AcquireLastForegroundCaptureBufferEx"},
+ {18, nullptr, "AcquireCallerAppletCaptureBufferEx"},
+ // 2.0.0+
+ {20, nullptr, "ClearCaptureBuffer"},
+ {21, nullptr, "ClearAppletTransitionBuffer"},
+ // 4.0.0+
+ {22, nullptr, "AcquireLastApplicationCaptureSharedBuffer"},
+ {23, nullptr, "ReleaseLastApplicationCaptureSharedBuffer"},
+ {24, nullptr, "AcquireLastForegroundCaptureSharedBuffer"},
+ {25, nullptr, "ReleaseLastForegroundCaptureSharedBuffer"},
+ {26, nullptr, "AcquireCallerAppletCaptureSharedBuffer"},
+ {27, nullptr, "ReleaseCallerAppletCaptureSharedBuffer"},
+ };
+ RegisterHandlers(functions);
+}
IDebugFunctions::IDebugFunctions() : ServiceFramework("IDebugFunctions") {}
ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger)
: ServiceFramework("ISelfController"), nvflinger(std::move(nvflinger)) {
static const FunctionInfo functions[] = {
+ {0, nullptr, "Exit"},
{1, &ISelfController::LockExit, "LockExit"},
{2, &ISelfController::UnlockExit, "UnlockExit"},
+ {3, nullptr, "EnterFatalSection"},
+ {4, nullptr, "LeaveFatalSection"},
{9, &ISelfController::GetLibraryAppletLaunchableEvent, "GetLibraryAppletLaunchableEvent"},
{10, &ISelfController::SetScreenShotPermission, "SetScreenShotPermission"},
{11, &ISelfController::SetOperationModeChangedNotification,
@@ -84,13 +128,34 @@ ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger
"SetPerformanceModeChangedNotification"},
{13, &ISelfController::SetFocusHandlingMode, "SetFocusHandlingMode"},
{14, &ISelfController::SetRestartMessageEnabled, "SetRestartMessageEnabled"},
+ {15, nullptr, "SetScreenShotAppletIdentityInfo"},
{16, &ISelfController::SetOutOfFocusSuspendingEnabled, "SetOutOfFocusSuspendingEnabled"},
+ {17, nullptr, "SetControllerFirmwareUpdateSection"},
+ {18, nullptr, "SetRequiresCaptureButtonShortPressedMessage"},
+ {19, nullptr, "SetScreenShotImageOrientation"},
+ {20, nullptr, "SetDesirableKeyboardLayout"},
{40, &ISelfController::CreateManagedDisplayLayer, "CreateManagedDisplayLayer"},
+ {41, nullptr, "IsSystemBufferSharingEnabled"},
+ {42, nullptr, "GetSystemSharedLayerHandle"},
+ {50, &ISelfController::SetHandlesRequestToDisplay, "SetHandlesRequestToDisplay"},
+ {51, nullptr, "ApproveToDisplay"},
+ {60, nullptr, "OverrideAutoSleepTimeAndDimmingTime"},
+ {61, nullptr, "SetMediaPlaybackState"},
+ {62, nullptr, "SetIdleTimeDetectionExtension"},
+ {63, nullptr, "GetIdleTimeDetectionExtension"},
+ {64, nullptr, "SetInputDetectionSourceSet"},
+ {65, nullptr, "ReportUserIsActive"},
+ {66, nullptr, "GetCurrentIlluminance"},
+ {67, nullptr, "IsIlluminanceAvailable"},
+ {68, nullptr, "SetAutoSleepDisabled"},
+ {69, nullptr, "IsAutoSleepDisabled"},
+ {70, nullptr, "ReportMultimediaError"},
+ {80, nullptr, "SetWirelessPriorityMode"},
};
RegisterHandlers(functions);
launchable_event =
- Kernel::Event::Create(Kernel::ResetType::OneShot, "ISelfController:LaunchableEvent");
+ Kernel::Event::Create(Kernel::ResetType::Sticky, "ISelfController:LaunchableEvent");
}
void ISelfController::SetFocusHandlingMode(Kernel::HLERequestContext& ctx) {
@@ -109,14 +174,14 @@ void ISelfController::SetFocusHandlingMode(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_AM, "(STUBBED) called");
+ NGLOG_WARNING(Service_AM, "(STUBBED) called");
}
void ISelfController::SetRestartMessageEnabled(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_AM, "(STUBBED) called");
+ NGLOG_WARNING(Service_AM, "(STUBBED) called");
}
void ISelfController::SetPerformanceModeChangedNotification(Kernel::HLERequestContext& ctx) {
@@ -127,14 +192,14 @@ void ISelfController::SetPerformanceModeChangedNotification(Kernel::HLERequestCo
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_AM, "(STUBBED) called flag=%u", static_cast<u32>(flag));
+ NGLOG_WARNING(Service_AM, "(STUBBED) called flag={}", flag);
}
void ISelfController::SetScreenShotPermission(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_AM, "(STUBBED) called");
+ NGLOG_WARNING(Service_AM, "(STUBBED) called");
}
void ISelfController::SetOperationModeChangedNotification(Kernel::HLERequestContext& ctx) {
@@ -145,7 +210,7 @@ void ISelfController::SetOperationModeChangedNotification(Kernel::HLERequestCont
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_AM, "(STUBBED) called flag=%u", static_cast<u32>(flag));
+ NGLOG_WARNING(Service_AM, "(STUBBED) called flag={}", flag);
}
void ISelfController::SetOutOfFocusSuspendingEnabled(Kernel::HLERequestContext& ctx) {
@@ -158,21 +223,21 @@ void ISelfController::SetOutOfFocusSuspendingEnabled(Kernel::HLERequestContext&
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_AM, "(STUBBED) called enabled=%u", static_cast<u32>(enabled));
+ NGLOG_WARNING(Service_AM, "(STUBBED) called enabled={}", enabled);
}
void ISelfController::LockExit(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_AM, "(STUBBED) called");
+ NGLOG_WARNING(Service_AM, "(STUBBED) called");
}
void ISelfController::UnlockExit(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_AM, "(STUBBED) called");
+ NGLOG_WARNING(Service_AM, "(STUBBED) called");
}
void ISelfController::GetLibraryAppletLaunchableEvent(Kernel::HLERequestContext& ctx) {
@@ -182,7 +247,7 @@ void ISelfController::GetLibraryAppletLaunchableEvent(Kernel::HLERequestContext&
rb.Push(RESULT_SUCCESS);
rb.PushCopyObjects(launchable_event);
- LOG_WARNING(Service_AM, "(STUBBED) called");
+ NGLOG_WARNING(Service_AM, "(STUBBED) called");
}
void ISelfController::CreateManagedDisplayLayer(Kernel::HLERequestContext& ctx) {
@@ -195,16 +260,44 @@ void ISelfController::CreateManagedDisplayLayer(Kernel::HLERequestContext& ctx)
rb.Push(RESULT_SUCCESS);
rb.Push(layer_id);
- LOG_WARNING(Service_AM, "(STUBBED) called");
+ NGLOG_WARNING(Service_AM, "(STUBBED) called");
+}
+
+void ISelfController::SetHandlesRequestToDisplay(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+
+ NGLOG_WARNING(Service_AM, "(STUBBED) called");
}
ICommonStateGetter::ICommonStateGetter() : ServiceFramework("ICommonStateGetter") {
static const FunctionInfo functions[] = {
{0, &ICommonStateGetter::GetEventHandle, "GetEventHandle"},
{1, &ICommonStateGetter::ReceiveMessage, "ReceiveMessage"},
+ {2, nullptr, "GetThisAppletKind"},
+ {3, nullptr, "AllowToEnterSleep"},
+ {4, nullptr, "DisallowToEnterSleep"},
{5, &ICommonStateGetter::GetOperationMode, "GetOperationMode"},
{6, &ICommonStateGetter::GetPerformanceMode, "GetPerformanceMode"},
+ {7, nullptr, "GetCradleStatus"},
+ {8, nullptr, "GetBootMode"},
{9, &ICommonStateGetter::GetCurrentFocusState, "GetCurrentFocusState"},
+ {10, nullptr, "RequestToAcquireSleepLock"},
+ {11, nullptr, "ReleaseSleepLock"},
+ {12, nullptr, "ReleaseSleepLockTransiently"},
+ {13, nullptr, "GetAcquiredSleepLockEvent"},
+ {20, nullptr, "PushToGeneralChannel"},
+ {30, nullptr, "GetHomeButtonReaderLockAccessor"},
+ {31, nullptr, "GetReaderLockAccessorEx"},
+ {40, nullptr, "GetCradleFwVersion"},
+ {50, nullptr, "IsVrModeEnabled"},
+ {51, nullptr, "SetVrModeEnabled"},
+ {52, nullptr, "SwitchLcdBacklight"},
+ {55, nullptr, "IsInControllerFirmwareUpdateSection"},
+ {60, nullptr, "GetDefaultDisplayResolution"},
+ {61, nullptr, "GetDefaultDisplayResolutionChangeEvent"},
+ {62, nullptr, "GetHdcpAuthenticationState"},
+ {63, nullptr, "GetHdcpAuthenticationStateChangeEvent"},
};
RegisterHandlers(functions);
@@ -218,7 +311,7 @@ void ICommonStateGetter::GetEventHandle(Kernel::HLERequestContext& ctx) {
rb.Push(RESULT_SUCCESS);
rb.PushCopyObjects(event);
- LOG_WARNING(Service_AM, "(STUBBED) called");
+ NGLOG_WARNING(Service_AM, "(STUBBED) called");
}
void ICommonStateGetter::ReceiveMessage(Kernel::HLERequestContext& ctx) {
@@ -226,7 +319,7 @@ void ICommonStateGetter::ReceiveMessage(Kernel::HLERequestContext& ctx) {
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(15);
- LOG_WARNING(Service_AM, "(STUBBED) called");
+ NGLOG_WARNING(Service_AM, "(STUBBED) called");
}
void ICommonStateGetter::GetCurrentFocusState(Kernel::HLERequestContext& ctx) {
@@ -234,44 +327,128 @@ void ICommonStateGetter::GetCurrentFocusState(Kernel::HLERequestContext& ctx) {
rb.Push(RESULT_SUCCESS);
rb.Push(static_cast<u8>(FocusState::InFocus));
- LOG_WARNING(Service_AM, "(STUBBED) called");
+ NGLOG_WARNING(Service_AM, "(STUBBED) called");
}
void ICommonStateGetter::GetOperationMode(Kernel::HLERequestContext& ctx) {
+ const bool use_docked_mode{Settings::values.use_docked_mode};
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- rb.Push(static_cast<u8>(OperationMode::Handheld));
+ rb.Push(static_cast<u8>(use_docked_mode ? OperationMode::Docked : OperationMode::Handheld));
- LOG_WARNING(Service_AM, "(STUBBED) called");
+ NGLOG_WARNING(Service_AM, "(STUBBED) called");
}
void ICommonStateGetter::GetPerformanceMode(Kernel::HLERequestContext& ctx) {
+ const bool use_docked_mode{Settings::values.use_docked_mode};
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- rb.Push(static_cast<u32>(APM::PerformanceMode::Handheld));
+ rb.Push(static_cast<u32>(use_docked_mode ? APM::PerformanceMode::Docked
+ : APM::PerformanceMode::Handheld));
- LOG_WARNING(Service_AM, "(STUBBED) called");
+ NGLOG_WARNING(Service_AM, "(STUBBED) called");
}
+class IStorageAccessor final : public ServiceFramework<IStorageAccessor> {
+public:
+ explicit IStorageAccessor(std::vector<u8> buffer)
+ : ServiceFramework("IStorageAccessor"), buffer(std::move(buffer)) {
+ static const FunctionInfo functions[] = {
+ {0, &IStorageAccessor::GetSize, "GetSize"},
+ {10, &IStorageAccessor::Write, "Write"},
+ {11, &IStorageAccessor::Read, "Read"},
+ };
+ RegisterHandlers(functions);
+ }
+
+private:
+ std::vector<u8> buffer;
+
+ void GetSize(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 4};
+
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(static_cast<u64>(buffer.size()));
+
+ NGLOG_DEBUG(Service_AM, "called");
+ }
+
+ void Write(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ const u64 offset{rp.Pop<u64>()};
+ const std::vector<u8> data{ctx.ReadBuffer()};
+
+ ASSERT(offset + data.size() <= buffer.size());
+
+ std::memcpy(&buffer[offset], data.data(), data.size());
+
+ IPC::ResponseBuilder rb{rp.MakeBuilder(2, 0, 0)};
+ rb.Push(RESULT_SUCCESS);
+
+ NGLOG_DEBUG(Service_AM, "called, offset={}", offset);
+ }
+
+ void Read(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ const u64 offset{rp.Pop<u64>()};
+ const size_t size{ctx.GetWriteBufferSize()};
+
+ ASSERT(offset + size <= buffer.size());
+
+ ctx.WriteBuffer(buffer.data() + offset, size);
+
+ IPC::ResponseBuilder rb{rp.MakeBuilder(2, 0, 0)};
+ rb.Push(RESULT_SUCCESS);
+
+ NGLOG_DEBUG(Service_AM, "called, offset={}", offset);
+ }
+};
+
+class IStorage final : public ServiceFramework<IStorage> {
+public:
+ explicit IStorage(std::vector<u8> buffer)
+ : ServiceFramework("IStorage"), buffer(std::move(buffer)) {
+ static const FunctionInfo functions[] = {
+ {0, &IStorage::Open, "Open"},
+ {1, nullptr, "OpenTransferStorage"},
+ };
+ RegisterHandlers(functions);
+ }
+
+private:
+ std::vector<u8> buffer;
+
+ void Open(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<AM::IStorageAccessor>(buffer);
+
+ NGLOG_DEBUG(Service_AM, "called");
+ }
+};
+
class ILibraryAppletAccessor final : public ServiceFramework<ILibraryAppletAccessor> {
public:
explicit ILibraryAppletAccessor() : ServiceFramework("ILibraryAppletAccessor") {
static const FunctionInfo functions[] = {
{0, &ILibraryAppletAccessor::GetAppletStateChangedEvent, "GetAppletStateChangedEvent"},
{1, nullptr, "IsCompleted"},
- {10, nullptr, "Start"},
+ {10, &ILibraryAppletAccessor::Start, "Start"},
{20, nullptr, "RequestExit"},
{25, nullptr, "Terminate"},
- {30, nullptr, "GetResult"},
+ {30, &ILibraryAppletAccessor::GetResult, "GetResult"},
{50, nullptr, "SetOutOfFocusApplicationSuspendingEnabled"},
- {100, nullptr, "PushInData"},
- {101, nullptr, "PopOutData"},
+ {100, &ILibraryAppletAccessor::PushInData, "PushInData"},
+ {101, &ILibraryAppletAccessor::PopOutData, "PopOutData"},
{102, nullptr, "PushExtraStorage"},
{103, nullptr, "PushInteractiveInData"},
{104, nullptr, "PopInteractiveOutData"},
{105, nullptr, "GetPopOutDataEvent"},
{106, nullptr, "GetPopInteractiveOutDataEvent"},
- {120, nullptr, "NeedsToExitProcess"},
+ {110, nullptr, "NeedsToExitProcess"},
{120, nullptr, "GetLibraryAppletInfo"},
{150, nullptr, "RequestForAppletToGetForeground"},
{160, nullptr, "GetIndirectLayerConsumerHandle"},
@@ -290,9 +467,44 @@ private:
rb.Push(RESULT_SUCCESS);
rb.PushCopyObjects(state_changed_event);
- LOG_WARNING(Service_AM, "(STUBBED) called");
+ NGLOG_WARNING(Service_AM, "(STUBBED) called");
+ }
+
+ void GetResult(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+
+ NGLOG_WARNING(Service_AM, "(STUBBED) called");
+ }
+
+ void Start(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+
+ NGLOG_WARNING(Service_AM, "(STUBBED) called");
+ }
+
+ void PushInData(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ storage_stack.push(rp.PopIpcInterface<AM::IStorage>());
+
+ IPC::ResponseBuilder rb{rp.MakeBuilder(2, 0, 0)};
+ rb.Push(RESULT_SUCCESS);
+
+ NGLOG_DEBUG(Service_AM, "called");
+ }
+
+ void PopOutData(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<AM::IStorage>(std::move(storage_stack.top()));
+
+ storage_stack.pop();
+
+ NGLOG_DEBUG(Service_AM, "called");
}
+ std::stack<std::shared_ptr<AM::IStorage>> storage_stack;
Kernel::SharedPtr<Kernel::Event> state_changed_event;
};
@@ -301,7 +513,7 @@ ILibraryAppletCreator::ILibraryAppletCreator() : ServiceFramework("ILibraryApple
{0, &ILibraryAppletCreator::CreateLibraryApplet, "CreateLibraryApplet"},
{1, nullptr, "TerminateAllLibraryApplets"},
{2, nullptr, "AreAnyLibraryAppletsLeft"},
- {10, nullptr, "CreateStorage"},
+ {10, &ILibraryAppletCreator::CreateStorage, "CreateStorage"},
{11, nullptr, "CreateTransferMemoryStorage"},
{12, nullptr, "CreateHandleStorage"},
};
@@ -314,83 +526,61 @@ void ILibraryAppletCreator::CreateLibraryApplet(Kernel::HLERequestContext& ctx)
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<AM::ILibraryAppletAccessor>();
- LOG_DEBUG(Service_AM, "called");
+ NGLOG_DEBUG(Service_AM, "called");
}
-class IStorageAccessor final : public ServiceFramework<IStorageAccessor> {
-public:
- explicit IStorageAccessor(std::vector<u8> buffer)
- : ServiceFramework("IStorageAccessor"), buffer(std::move(buffer)) {
- static const FunctionInfo functions[] = {
- {0, &IStorageAccessor::GetSize, "GetSize"},
- {11, &IStorageAccessor::Read, "Read"},
- };
- RegisterHandlers(functions);
- }
-
-private:
- std::vector<u8> buffer;
-
- void GetSize(Kernel::HLERequestContext& ctx) {
- IPC::ResponseBuilder rb{ctx, 4};
-
- rb.Push(RESULT_SUCCESS);
- rb.Push(static_cast<u64>(buffer.size()));
-
- LOG_DEBUG(Service_AM, "called");
- }
-
- void Read(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
-
- u64 offset = rp.Pop<u64>();
-
- const size_t size{ctx.GetWriteBufferSize()};
-
- ASSERT(offset + size <= buffer.size());
-
- ctx.WriteBuffer(buffer.data() + offset, size);
-
- IPC::ResponseBuilder rb{ctx, 2};
-
- rb.Push(RESULT_SUCCESS);
-
- LOG_DEBUG(Service_AM, "called");
- }
-};
-
-class IStorage final : public ServiceFramework<IStorage> {
-public:
- explicit IStorage(std::vector<u8> buffer)
- : ServiceFramework("IStorage"), buffer(std::move(buffer)) {
- static const FunctionInfo functions[] = {
- {0, &IStorage::Open, "Open"},
- };
- RegisterHandlers(functions);
- }
-
-private:
- std::vector<u8> buffer;
-
- void Open(Kernel::HLERequestContext& ctx) {
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+void ILibraryAppletCreator::CreateStorage(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const u64 size{rp.Pop<u64>()};
+ std::vector<u8> buffer(size);
- rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<AM::IStorageAccessor>(buffer);
+ IPC::ResponseBuilder rb{rp.MakeBuilder(2, 0, 1)};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<AM::IStorage>(std::move(buffer));
- LOG_DEBUG(Service_AM, "called");
- }
-};
+ NGLOG_DEBUG(Service_AM, "called, size={}", size);
+}
IApplicationFunctions::IApplicationFunctions() : ServiceFramework("IApplicationFunctions") {
static const FunctionInfo functions[] = {
{1, &IApplicationFunctions::PopLaunchParameter, "PopLaunchParameter"},
+ {10, nullptr, "CreateApplicationAndPushAndRequestToStart"},
+ {11, nullptr, "CreateApplicationAndPushAndRequestToStartForQuest"},
+ {12, nullptr, "CreateApplicationAndRequestToStart"},
+ {13, &IApplicationFunctions::CreateApplicationAndRequestToStartForQuest,
+ "CreateApplicationAndRequestToStartForQuest"},
{20, &IApplicationFunctions::EnsureSaveData, "EnsureSaveData"},
{21, &IApplicationFunctions::GetDesiredLanguage, "GetDesiredLanguage"},
{22, &IApplicationFunctions::SetTerminateResult, "SetTerminateResult"},
+ {23, &IApplicationFunctions::GetDisplayVersion, "GetDisplayVersion"},
+ {24, nullptr, "GetLaunchStorageInfoForDebug"},
+ {25, nullptr, "ExtendSaveData"},
+ {26, nullptr, "GetSaveDataSize"},
+ {30, nullptr, "BeginBlockingHomeButtonShortAndLongPressed"},
+ {31, nullptr, "EndBlockingHomeButtonShortAndLongPressed"},
+ {32, nullptr, "BeginBlockingHomeButton"},
+ {33, nullptr, "EndBlockingHomeButton"},
+ {40, &IApplicationFunctions::NotifyRunning, "NotifyRunning"},
+ {50, &IApplicationFunctions::GetPseudoDeviceId, "GetPseudoDeviceId"},
+ {60, nullptr, "SetMediaPlaybackStateForApplication"},
+ {65, nullptr, "IsGamePlayRecordingSupported"},
{66, &IApplicationFunctions::InitializeGamePlayRecording, "InitializeGamePlayRecording"},
{67, &IApplicationFunctions::SetGamePlayRecordingState, "SetGamePlayRecordingState"},
- {40, &IApplicationFunctions::NotifyRunning, "NotifyRunning"},
+ {68, nullptr, "RequestFlushGamePlayingMovieForDebug"},
+ {70, nullptr, "RequestToShutdown"},
+ {71, nullptr, "RequestToReboot"},
+ {80, nullptr, "ExitAndRequestToShowThanksMessage"},
+ {90, nullptr, "EnableApplicationCrashReport"},
+ {100, nullptr, "InitializeApplicationCopyrightFrameBuffer"},
+ {101, nullptr, "SetApplicationCopyrightImage"},
+ {102, nullptr, "SetApplicationCopyrightVisibility"},
+ {110, nullptr, "QueryApplicationPlayStatistics"},
+ {120, nullptr, "ExecuteProgram"},
+ {121, nullptr, "ClearUserChannel"},
+ {122, nullptr, "UnpopToUserChannel"},
+ {500, nullptr, "StartContinuousRecordingFlushForDebug"},
+ {1000, nullptr, "CreateMovieMaker"},
+ {1001, nullptr, "PrepareForJit"},
};
RegisterHandlers(functions);
}
@@ -412,13 +602,35 @@ void IApplicationFunctions::PopLaunchParameter(Kernel::HLERequestContext& ctx) {
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<AM::IStorage>(buffer);
- LOG_DEBUG(Service_AM, "called");
+ NGLOG_DEBUG(Service_AM, "called");
+}
+
+void IApplicationFunctions::CreateApplicationAndRequestToStartForQuest(
+ Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ NGLOG_WARNING(Service_AM, "(STUBBED) called");
}
void IApplicationFunctions::EnsureSaveData(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ u128 uid = rp.PopRaw<u128>();
+
+ NGLOG_WARNING(Service, "(STUBBED) called uid = {:016X}{:016X}", uid[1], uid[0]);
+
IPC::ResponseBuilder rb{ctx, 4};
- rb.Push(RESULT_SUCCESS);
+
+ FileSys::Path unused;
+ auto savedata = FileSystem::OpenFileSystem(FileSystem::Type::SaveData, unused);
+ if (savedata.Failed()) {
+ // Create the save data and return an error indicating that the operation was performed.
+ FileSystem::FormatFileSystem(FileSystem::Type::SaveData);
+ // TODO(Subv): Find out the correct error code for this.
+ rb.Push(ResultCode(ErrorModule::FS, 40));
+ } else {
+ rb.Push(RESULT_SUCCESS);
+ }
+
rb.Push<u64>(0);
}
@@ -432,27 +644,36 @@ void IApplicationFunctions::SetTerminateResult(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_AM, "(STUBBED) called, result=0x%08X", result);
+ NGLOG_WARNING(Service_AM, "(STUBBED) called, result=0x{:08X}", result);
+}
+
+void IApplicationFunctions::GetDisplayVersion(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 6};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u64>(1);
+ rb.Push<u64>(0);
+ NGLOG_WARNING(Service_AM, "(STUBBED) called");
}
void IApplicationFunctions::GetDesiredLanguage(Kernel::HLERequestContext& ctx) {
+ // TODO(bunnei): This should be configurable
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
- rb.Push<u64>(SystemLanguage::English);
- LOG_WARNING(Service_AM, "(STUBBED) called");
+ rb.Push(static_cast<u64>(Service::Set::LanguageCode::EN_US));
+ NGLOG_DEBUG(Service_AM, "called");
}
void IApplicationFunctions::InitializeGamePlayRecording(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_AM, "(STUBBED) called");
+ NGLOG_WARNING(Service_AM, "(STUBBED) called");
}
void IApplicationFunctions::SetGamePlayRecordingState(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_AM, "(STUBBED) called");
+ NGLOG_WARNING(Service_AM, "(STUBBED) called");
}
void IApplicationFunctions::NotifyRunning(Kernel::HLERequestContext& ctx) {
@@ -460,7 +681,18 @@ void IApplicationFunctions::NotifyRunning(Kernel::HLERequestContext& ctx) {
rb.Push(RESULT_SUCCESS);
rb.Push<u8>(0); // Unknown, seems to be ignored by official processes
- LOG_WARNING(Service_AM, "(STUBBED) called");
+ NGLOG_WARNING(Service_AM, "(STUBBED) called");
+}
+
+void IApplicationFunctions::GetPseudoDeviceId(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 6};
+ rb.Push(RESULT_SUCCESS);
+
+ // Returns a 128-bit UUID
+ rb.Push<u64>(0);
+ rb.Push<u64>(0);
+
+ NGLOG_WARNING(Service_AM, "(STUBBED) called");
}
void InstallInterfaces(SM::ServiceManager& service_manager,
@@ -469,5 +701,64 @@ void InstallInterfaces(SM::ServiceManager& service_manager,
std::make_shared<AppletOE>(nvflinger)->InstallAsService(service_manager);
}
-} // namespace AM
-} // namespace Service
+IHomeMenuFunctions::IHomeMenuFunctions() : ServiceFramework("IHomeMenuFunctions") {
+ static const FunctionInfo functions[] = {
+ {10, &IHomeMenuFunctions::RequestToGetForeground, "RequestToGetForeground"},
+ {11, nullptr, "LockForeground"},
+ {12, nullptr, "UnlockForeground"},
+ {20, nullptr, "PopFromGeneralChannel"},
+ {21, nullptr, "GetPopFromGeneralChannelEvent"},
+ {30, nullptr, "GetHomeButtonWriterLockAccessor"},
+ {31, nullptr, "GetWriterLockAccessorEx"},
+ };
+ RegisterHandlers(functions);
+}
+
+void IHomeMenuFunctions::RequestToGetForeground(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ NGLOG_WARNING(Service_AM, "(STUBBED) called");
+}
+
+IGlobalStateController::IGlobalStateController() : ServiceFramework("IGlobalStateController") {
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "RequestToEnterSleep"},
+ {1, nullptr, "EnterSleep"},
+ {2, nullptr, "StartSleepSequence"},
+ {3, nullptr, "StartShutdownSequence"},
+ {4, nullptr, "StartRebootSequence"},
+ {10, nullptr, "LoadAndApplyIdlePolicySettings"},
+ {11, nullptr, "NotifyCecSettingsChanged"},
+ {12, nullptr, "SetDefaultHomeButtonLongPressTime"},
+ {13, nullptr, "UpdateDefaultDisplayResolution"},
+ {14, nullptr, "ShouldSleepOnBoot"},
+ {15, nullptr, "GetHdcpAuthenticationFailedEvent"},
+ };
+ RegisterHandlers(functions);
+}
+
+IApplicationCreator::IApplicationCreator() : ServiceFramework("IApplicationCreator") {
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "CreateApplication"},
+ {1, nullptr, "PopLaunchRequestedApplication"},
+ {10, nullptr, "CreateSystemApplication"},
+ {100, nullptr, "PopFloatingApplicationForDevelopment"},
+ };
+ RegisterHandlers(functions);
+}
+
+IProcessWindingController::IProcessWindingController()
+ : ServiceFramework("IProcessWindingController") {
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "GetLaunchReason"},
+ {11, nullptr, "OpenCallingLibraryApplet"},
+ {21, nullptr, "PushContext"},
+ {22, nullptr, "PopContext"},
+ {23, nullptr, "CancelWindingReservation"},
+ {30, nullptr, "WindAndDoReserved"},
+ {40, nullptr, "ReserveToStartAndWaitAndUnwindThis"},
+ {41, nullptr, "ReserveToStartAndWait"},
+ };
+ RegisterHandlers(functions);
+}
+} // namespace Service::AM
diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h
index 27dbd8c95..1da79fd01 100644
--- a/src/core/hle/service/am/am.h
+++ b/src/core/hle/service/am/am.h
@@ -18,10 +18,25 @@ class NVFlinger;
namespace AM {
-// TODO: Add more languages
enum SystemLanguage {
Japanese = 0,
- English = 1,
+ English = 1, // en-US
+ French = 2,
+ German = 3,
+ Italian = 4,
+ Spanish = 5,
+ Chinese = 6,
+ Korean = 7,
+ Dutch = 8,
+ Portuguese = 9,
+ Russian = 10,
+ Taiwanese = 11,
+ BritishEnglish = 12, // en-GB
+ CanadianFrench = 13,
+ LatinAmericanSpanish = 14, // es-419
+ // 4.0.0+
+ SimplifiedChinese = 15,
+ TraditionalChinese = 16,
};
class IWindowController final : public ServiceFramework<IWindowController> {
@@ -70,6 +85,7 @@ private:
void GetLibraryAppletLaunchableEvent(Kernel::HLERequestContext& ctx);
void CreateManagedDisplayLayer(Kernel::HLERequestContext& ctx);
void SetScreenShotPermission(Kernel::HLERequestContext& ctx);
+ void SetHandlesRequestToDisplay(Kernel::HLERequestContext& ctx);
std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
Kernel::SharedPtr<Kernel::Event> launchable_event;
@@ -105,6 +121,7 @@ public:
private:
void CreateLibraryApplet(Kernel::HLERequestContext& ctx);
+ void CreateStorage(Kernel::HLERequestContext& ctx);
};
class IApplicationFunctions final : public ServiceFramework<IApplicationFunctions> {
@@ -113,12 +130,38 @@ public:
private:
void PopLaunchParameter(Kernel::HLERequestContext& ctx);
+ void CreateApplicationAndRequestToStartForQuest(Kernel::HLERequestContext& ctx);
void EnsureSaveData(Kernel::HLERequestContext& ctx);
void SetTerminateResult(Kernel::HLERequestContext& ctx);
+ void GetDisplayVersion(Kernel::HLERequestContext& ctx);
void GetDesiredLanguage(Kernel::HLERequestContext& ctx);
void InitializeGamePlayRecording(Kernel::HLERequestContext& ctx);
void SetGamePlayRecordingState(Kernel::HLERequestContext& ctx);
void NotifyRunning(Kernel::HLERequestContext& ctx);
+ void GetPseudoDeviceId(Kernel::HLERequestContext& ctx);
+};
+
+class IHomeMenuFunctions final : public ServiceFramework<IHomeMenuFunctions> {
+public:
+ IHomeMenuFunctions();
+
+private:
+ void RequestToGetForeground(Kernel::HLERequestContext& ctx);
+};
+
+class IGlobalStateController final : public ServiceFramework<IGlobalStateController> {
+public:
+ IGlobalStateController();
+};
+
+class IApplicationCreator final : public ServiceFramework<IApplicationCreator> {
+public:
+ IApplicationCreator();
+};
+
+class IProcessWindingController final : public ServiceFramework<IProcessWindingController> {
+public:
+ IProcessWindingController();
};
/// Registers all AM services with the specified service manager.
diff --git a/src/core/hle/service/am/applet_ae.cpp b/src/core/hle/service/am/applet_ae.cpp
index 0e51caa70..7ce551de3 100644
--- a/src/core/hle/service/am/applet_ae.cpp
+++ b/src/core/hle/service/am/applet_ae.cpp
@@ -8,8 +8,7 @@
#include "core/hle/service/am/applet_ae.h"
#include "core/hle/service/nvflinger/nvflinger.h"
-namespace Service {
-namespace AM {
+namespace Service::AM {
class ILibraryAppletProxy final : public ServiceFramework<ILibraryAppletProxy> {
public:
@@ -21,6 +20,7 @@ public:
{2, &ILibraryAppletProxy::GetWindowController, "GetWindowController"},
{3, &ILibraryAppletProxy::GetAudioController, "GetAudioController"},
{4, &ILibraryAppletProxy::GetDisplayController, "GetDisplayController"},
+ {10, &ILibraryAppletProxy::GetProcessWindingController, "GetProcessWindingController"},
{11, &ILibraryAppletProxy::GetLibraryAppletCreator, "GetLibraryAppletCreator"},
{20, &ILibraryAppletProxy::GetApplicationFunctions, "GetApplicationFunctions"},
{1000, &ILibraryAppletProxy::GetDebugFunctions, "GetDebugFunctions"},
@@ -33,74 +33,188 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<ICommonStateGetter>();
- LOG_DEBUG(Service_AM, "called");
+ NGLOG_DEBUG(Service_AM, "called");
}
void GetSelfController(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<ISelfController>(nvflinger);
- LOG_DEBUG(Service_AM, "called");
+ NGLOG_DEBUG(Service_AM, "called");
}
void GetWindowController(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IWindowController>();
- LOG_DEBUG(Service_AM, "called");
+ NGLOG_DEBUG(Service_AM, "called");
}
void GetAudioController(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IAudioController>();
- LOG_DEBUG(Service_AM, "called");
+ NGLOG_DEBUG(Service_AM, "called");
}
void GetDisplayController(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IDisplayController>();
- LOG_DEBUG(Service_AM, "called");
+ NGLOG_DEBUG(Service_AM, "called");
+ }
+
+ void GetProcessWindingController(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IProcessWindingController>();
+ NGLOG_DEBUG(Service_AM, "called");
}
void GetDebugFunctions(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IDebugFunctions>();
- LOG_DEBUG(Service_AM, "called");
+ NGLOG_DEBUG(Service_AM, "called");
}
void GetLibraryAppletCreator(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<ILibraryAppletCreator>();
- LOG_DEBUG(Service_AM, "called");
+ NGLOG_DEBUG(Service_AM, "called");
}
void GetApplicationFunctions(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IApplicationFunctions>();
- LOG_DEBUG(Service_AM, "called");
+ NGLOG_DEBUG(Service_AM, "called");
+ }
+
+ std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
+};
+
+class ISystemAppletProxy final : public ServiceFramework<ISystemAppletProxy> {
+public:
+ explicit ISystemAppletProxy(std::shared_ptr<NVFlinger::NVFlinger> nvflinger)
+ : ServiceFramework("ISystemAppletProxy"), nvflinger(std::move(nvflinger)) {
+ static const FunctionInfo functions[] = {
+ {0, &ISystemAppletProxy::GetCommonStateGetter, "GetCommonStateGetter"},
+ {1, &ISystemAppletProxy::GetSelfController, "GetSelfController"},
+ {2, &ISystemAppletProxy::GetWindowController, "GetWindowController"},
+ {3, &ISystemAppletProxy::GetAudioController, "GetAudioController"},
+ {4, &ISystemAppletProxy::GetDisplayController, "GetDisplayController"},
+ {10, nullptr, "GetProcessWindingController"},
+ {11, &ISystemAppletProxy::GetLibraryAppletCreator, "GetLibraryAppletCreator"},
+ {20, &ISystemAppletProxy::GetHomeMenuFunctions, "GetHomeMenuFunctions"},
+ {21, &ISystemAppletProxy::GetGlobalStateController, "GetGlobalStateController"},
+ {22, &ISystemAppletProxy::GetApplicationCreator, "GetApplicationCreator"},
+ {1000, &ISystemAppletProxy::GetDebugFunctions, "GetDebugFunctions"},
+ };
+ RegisterHandlers(functions);
+ }
+
+private:
+ void GetCommonStateGetter(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<ICommonStateGetter>();
+ NGLOG_DEBUG(Service_AM, "called");
+ }
+
+ void GetSelfController(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<ISelfController>(nvflinger);
+ NGLOG_DEBUG(Service_AM, "called");
+ }
+
+ void GetWindowController(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IWindowController>();
+ NGLOG_DEBUG(Service_AM, "called");
+ }
+
+ void GetAudioController(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IAudioController>();
+ NGLOG_DEBUG(Service_AM, "called");
}
+ void GetDisplayController(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IDisplayController>();
+ NGLOG_DEBUG(Service_AM, "called");
+ }
+
+ void GetDebugFunctions(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IDebugFunctions>();
+ NGLOG_DEBUG(Service_AM, "called");
+ }
+
+ void GetLibraryAppletCreator(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<ILibraryAppletCreator>();
+ NGLOG_DEBUG(Service_AM, "called");
+ }
+
+ void GetHomeMenuFunctions(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IHomeMenuFunctions>();
+ NGLOG_DEBUG(Service_AM, "called");
+ }
+
+ void GetGlobalStateController(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IGlobalStateController>();
+ NGLOG_DEBUG(Service_AM, "called");
+ }
+
+ void GetApplicationCreator(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IApplicationCreator>();
+ NGLOG_DEBUG(Service_AM, "called");
+ }
std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
};
+void AppletAE::OpenSystemAppletProxy(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<ISystemAppletProxy>(nvflinger);
+ NGLOG_DEBUG(Service_AM, "called");
+}
+
+void AppletAE::OpenLibraryAppletProxy(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<ILibraryAppletProxy>(nvflinger);
+ NGLOG_DEBUG(Service_AM, "called");
+}
+
void AppletAE::OpenLibraryAppletProxyOld(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<ILibraryAppletProxy>(nvflinger);
- LOG_DEBUG(Service_AM, "called");
+ NGLOG_DEBUG(Service_AM, "called");
}
AppletAE::AppletAE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger)
: ServiceFramework("appletAE"), nvflinger(std::move(nvflinger)) {
static const FunctionInfo functions[] = {
- {100, nullptr, "OpenSystemAppletProxy"},
+ {100, &AppletAE::OpenSystemAppletProxy, "OpenSystemAppletProxy"},
{200, &AppletAE::OpenLibraryAppletProxyOld, "OpenLibraryAppletProxyOld"},
- {201, nullptr, "OpenLibraryAppletProxy"},
+ {201, &AppletAE::OpenLibraryAppletProxy, "OpenLibraryAppletProxy"},
{300, nullptr, "OpenOverlayAppletProxy"},
{350, nullptr, "OpenSystemApplicationProxy"},
{400, nullptr, "CreateSelfLibraryAppletCreatorForDevelop"},
@@ -108,5 +222,4 @@ AppletAE::AppletAE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger)
RegisterHandlers(functions);
}
-} // namespace AM
-} // namespace Service
+} // namespace Service::AM
diff --git a/src/core/hle/service/am/applet_ae.h b/src/core/hle/service/am/applet_ae.h
index 38fc428fb..f3a96651e 100644
--- a/src/core/hle/service/am/applet_ae.h
+++ b/src/core/hle/service/am/applet_ae.h
@@ -21,6 +21,8 @@ public:
~AppletAE() = default;
private:
+ void OpenSystemAppletProxy(Kernel::HLERequestContext& ctx);
+ void OpenLibraryAppletProxy(Kernel::HLERequestContext& ctx);
void OpenLibraryAppletProxyOld(Kernel::HLERequestContext& ctx);
std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
diff --git a/src/core/hle/service/am/applet_oe.cpp b/src/core/hle/service/am/applet_oe.cpp
index bdcebe689..587a922fe 100644
--- a/src/core/hle/service/am/applet_oe.cpp
+++ b/src/core/hle/service/am/applet_oe.cpp
@@ -8,8 +8,7 @@
#include "core/hle/service/am/applet_oe.h"
#include "core/hle/service/nvflinger/nvflinger.h"
-namespace Service {
-namespace AM {
+namespace Service::AM {
class IApplicationProxy final : public ServiceFramework<IApplicationProxy> {
public:
@@ -21,6 +20,7 @@ public:
{2, &IApplicationProxy::GetWindowController, "GetWindowController"},
{3, &IApplicationProxy::GetAudioController, "GetAudioController"},
{4, &IApplicationProxy::GetDisplayController, "GetDisplayController"},
+ {10, nullptr, "GetProcessWindingController"},
{11, &IApplicationProxy::GetLibraryAppletCreator, "GetLibraryAppletCreator"},
{20, &IApplicationProxy::GetApplicationFunctions, "GetApplicationFunctions"},
{1000, &IApplicationProxy::GetDebugFunctions, "GetDebugFunctions"},
@@ -33,56 +33,56 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IAudioController>();
- LOG_DEBUG(Service_AM, "called");
+ NGLOG_DEBUG(Service_AM, "called");
}
void GetDisplayController(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IDisplayController>();
- LOG_DEBUG(Service_AM, "called");
+ NGLOG_DEBUG(Service_AM, "called");
}
void GetDebugFunctions(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IDebugFunctions>();
- LOG_DEBUG(Service_AM, "called");
+ NGLOG_DEBUG(Service_AM, "called");
}
void GetWindowController(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IWindowController>();
- LOG_DEBUG(Service_AM, "called");
+ NGLOG_DEBUG(Service_AM, "called");
}
void GetSelfController(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<ISelfController>(nvflinger);
- LOG_DEBUG(Service_AM, "called");
+ NGLOG_DEBUG(Service_AM, "called");
}
void GetCommonStateGetter(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<ICommonStateGetter>();
- LOG_DEBUG(Service_AM, "called");
+ NGLOG_DEBUG(Service_AM, "called");
}
void GetLibraryAppletCreator(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<ILibraryAppletCreator>();
- LOG_DEBUG(Service_AM, "called");
+ NGLOG_DEBUG(Service_AM, "called");
}
void GetApplicationFunctions(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IApplicationFunctions>();
- LOG_DEBUG(Service_AM, "called");
+ NGLOG_DEBUG(Service_AM, "called");
}
std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
@@ -92,16 +92,15 @@ void AppletOE::OpenApplicationProxy(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IApplicationProxy>(nvflinger);
- LOG_DEBUG(Service_AM, "called");
+ NGLOG_DEBUG(Service_AM, "called");
}
AppletOE::AppletOE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger)
: ServiceFramework("appletOE"), nvflinger(std::move(nvflinger)) {
static const FunctionInfo functions[] = {
- {0x00000000, &AppletOE::OpenApplicationProxy, "OpenApplicationProxy"},
+ {0, &AppletOE::OpenApplicationProxy, "OpenApplicationProxy"},
};
RegisterHandlers(functions);
}
-} // namespace AM
-} // namespace Service
+} // namespace Service::AM
diff --git a/src/core/hle/service/aoc/aoc_u.cpp b/src/core/hle/service/aoc/aoc_u.cpp
index 8b55d2fcb..5b6dfb48f 100644
--- a/src/core/hle/service/aoc/aoc_u.cpp
+++ b/src/core/hle/service/aoc/aoc_u.cpp
@@ -6,8 +6,7 @@
#include "core/hle/ipc_helpers.h"
#include "core/hle/service/aoc/aoc_u.h"
-namespace Service {
-namespace AOC {
+namespace Service::AOC {
AOC_U::AOC_U() : ServiceFramework("aoc:u") {
static const FunctionInfo functions[] = {
@@ -19,6 +18,7 @@ AOC_U::AOC_U() : ServiceFramework("aoc:u") {
{5, nullptr, "GetAddOnContentBaseId"},
{6, nullptr, "PrepareAddOnContentByApplicationId"},
{7, nullptr, "PrepareAddOnContent"},
+ {8, nullptr, "GetAddOnContentListChangedEvent"},
};
RegisterHandlers(functions);
}
@@ -27,19 +27,18 @@ void AOC_U::CountAddOnContent(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
rb.Push<u64>(0);
- LOG_WARNING(Service_AOC, "(STUBBED) called");
+ NGLOG_WARNING(Service_AOC, "(STUBBED) called");
}
void AOC_U::ListAddOnContent(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
rb.Push<u64>(0);
- LOG_WARNING(Service_AOC, "(STUBBED) called");
+ NGLOG_WARNING(Service_AOC, "(STUBBED) called");
}
void InstallInterfaces(SM::ServiceManager& service_manager) {
std::make_shared<AOC_U>()->InstallAsService(service_manager);
}
-} // namespace AOC
-} // namespace Service
+} // namespace Service::AOC
diff --git a/src/core/hle/service/aoc/aoc_u.h b/src/core/hle/service/aoc/aoc_u.h
index 6e0ba15a5..17d48ef30 100644
--- a/src/core/hle/service/aoc/aoc_u.h
+++ b/src/core/hle/service/aoc/aoc_u.h
@@ -6,8 +6,7 @@
#include "core/hle/service/service.h"
-namespace Service {
-namespace AOC {
+namespace Service::AOC {
class AOC_U final : public ServiceFramework<AOC_U> {
public:
@@ -22,5 +21,4 @@ private:
/// Registers all AOC services with the specified service manager.
void InstallInterfaces(SM::ServiceManager& service_manager);
-} // namespace AOC
-} // namespace Service
+} // namespace Service::AOC
diff --git a/src/core/hle/service/apm/apm.cpp b/src/core/hle/service/apm/apm.cpp
index c4b09b435..7a185c6c8 100644
--- a/src/core/hle/service/apm/apm.cpp
+++ b/src/core/hle/service/apm/apm.cpp
@@ -7,8 +7,7 @@
#include "core/hle/service/apm/apm.h"
#include "core/hle/service/apm/interface.h"
-namespace Service {
-namespace APM {
+namespace Service::APM {
void InstallInterfaces(SM::ServiceManager& service_manager) {
auto module_ = std::make_shared<Module>();
@@ -16,5 +15,4 @@ void InstallInterfaces(SM::ServiceManager& service_manager) {
std::make_shared<APM>(module_, "apm:p")->InstallAsService(service_manager);
}
-} // namespace APM
-} // namespace Service
+} // namespace Service::APM
diff --git a/src/core/hle/service/apm/apm.h b/src/core/hle/service/apm/apm.h
index 070ab21f8..90a80d51b 100644
--- a/src/core/hle/service/apm/apm.h
+++ b/src/core/hle/service/apm/apm.h
@@ -6,8 +6,7 @@
#include "core/hle/service/service.h"
-namespace Service {
-namespace APM {
+namespace Service::APM {
enum class PerformanceMode : u8 {
Handheld = 0,
@@ -23,5 +22,4 @@ public:
/// Registers all AM services with the specified service manager.
void InstallInterfaces(SM::ServiceManager& service_manager);
-} // namespace APM
-} // namespace Service
+} // namespace Service::APM
diff --git a/src/core/hle/service/apm/interface.cpp b/src/core/hle/service/apm/interface.cpp
index 0179351ba..3a03188ce 100644
--- a/src/core/hle/service/apm/interface.cpp
+++ b/src/core/hle/service/apm/interface.cpp
@@ -7,8 +7,7 @@
#include "core/hle/service/apm/apm.h"
#include "core/hle/service/apm/interface.h"
-namespace Service {
-namespace APM {
+namespace Service::APM {
class ISession final : public ServiceFramework<ISession> {
public:
@@ -30,8 +29,8 @@ private:
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_APM, "(STUBBED) called mode=%u config=%u", static_cast<u32>(mode),
- config);
+ NGLOG_WARNING(Service_APM, "(STUBBED) called mode={} config={}", static_cast<u32>(mode),
+ config);
}
void GetPerformanceConfiguration(Kernel::HLERequestContext& ctx) {
@@ -43,7 +42,7 @@ private:
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(0); // Performance configuration
- LOG_WARNING(Service_APM, "(STUBBED) called mode=%u", static_cast<u32>(mode));
+ NGLOG_WARNING(Service_APM, "(STUBBED) called mode={}", static_cast<u32>(mode));
}
};
@@ -62,5 +61,4 @@ void APM::OpenSession(Kernel::HLERequestContext& ctx) {
rb.PushIpcInterface<ISession>();
}
-} // namespace APM
-} // namespace Service
+} // namespace Service::APM
diff --git a/src/core/hle/service/apm/interface.h b/src/core/hle/service/apm/interface.h
index 7d53721de..b99dbb412 100644
--- a/src/core/hle/service/apm/interface.h
+++ b/src/core/hle/service/apm/interface.h
@@ -6,8 +6,7 @@
#include "core/hle/service/service.h"
-namespace Service {
-namespace APM {
+namespace Service::APM {
class APM final : public ServiceFramework<APM> {
public:
@@ -23,5 +22,4 @@ private:
/// Registers all AM services with the specified service manager.
void InstallInterfaces(SM::ServiceManager& service_manager);
-} // namespace APM
-} // namespace Service
+} // namespace Service::APM
diff --git a/src/core/hle/service/audio/audin_u.cpp b/src/core/hle/service/audio/audin_u.cpp
index ee749fddd..cbc49e55e 100644
--- a/src/core/hle/service/audio/audin_u.cpp
+++ b/src/core/hle/service/audio/audin_u.cpp
@@ -7,22 +7,26 @@
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/service/audio/audin_u.h"
-namespace Service {
-namespace Audio {
+namespace Service::Audio {
class IAudioIn final : public ServiceFramework<IAudioIn> {
public:
IAudioIn() : ServiceFramework("IAudioIn") {
static const FunctionInfo functions[] = {
- {0x0, nullptr, "GetAudioInState"},
- {0x1, nullptr, "StartAudioIn"},
- {0x2, nullptr, "StopAudioIn"},
- {0x3, nullptr, "AppendAudioInBuffer_1"},
- {0x4, nullptr, "RegisterBufferEvent"},
- {0x5, nullptr, "GetReleasedAudioInBuffer_1"},
- {0x6, nullptr, "ContainsAudioInBuffer"},
- {0x7, nullptr, "AppendAudioInBuffer_2"},
- {0x8, nullptr, "GetReleasedAudioInBuffer_2"},
+ {0, nullptr, "GetAudioInState"},
+ {1, nullptr, "StartAudioIn"},
+ {2, nullptr, "StopAudioIn"},
+ {3, nullptr, "AppendAudioInBuffer"},
+ {4, nullptr, "RegisterBufferEvent"},
+ {5, nullptr, "GetReleasedAudioInBuffer"},
+ {6, nullptr, "ContainsAudioInBuffer"},
+ {7, nullptr, "AppendAudioInBufferWithUserEvent"},
+ {8, nullptr, "AppendAudioInBufferAuto"},
+ {9, nullptr, "GetReleasedAudioInBufferAuto"},
+ {10, nullptr, "AppendAudioInBufferWithUserEventAuto"},
+ {11, nullptr, "GetAudioInBufferCount"},
+ {12, nullptr, "SetAudioInDeviceGain"},
+ {13, nullptr, "GetAudioInDeviceGain"},
};
RegisterHandlers(functions);
}
@@ -31,11 +35,10 @@ public:
AudInU::AudInU() : ServiceFramework("audin:u") {
static const FunctionInfo functions[] = {
- {0x00000000, nullptr, "ListAudioIns"},
- {0x00000001, nullptr, "OpenAudioIn"},
+ {0, nullptr, "ListAudioIns"}, {1, nullptr, "OpenAudioIn"}, {2, nullptr, "Unknown"},
+ {3, nullptr, "OpenAudioInAuto"}, {4, nullptr, "ListAudioInsAuto"},
};
RegisterHandlers(functions);
}
-} // namespace Audio
-} // namespace Service
+} // namespace Service::Audio
diff --git a/src/core/hle/service/audio/audin_u.h b/src/core/hle/service/audio/audin_u.h
index 2b8576756..2e65efb5b 100644
--- a/src/core/hle/service/audio/audin_u.h
+++ b/src/core/hle/service/audio/audin_u.h
@@ -10,8 +10,7 @@ namespace Kernel {
class HLERequestContext;
}
-namespace Service {
-namespace Audio {
+namespace Service::Audio {
class AudInU final : public ServiceFramework<AudInU> {
public:
@@ -19,5 +18,4 @@ public:
~AudInU() = default;
};
-} // namespace Audio
-} // namespace Service
+} // namespace Service::Audio
diff --git a/src/core/hle/service/audio/audio.cpp b/src/core/hle/service/audio/audio.cpp
index 3f7fb44eb..92f910b5f 100644
--- a/src/core/hle/service/audio/audio.cpp
+++ b/src/core/hle/service/audio/audio.cpp
@@ -9,8 +9,7 @@
#include "core/hle/service/audio/audren_u.h"
#include "core/hle/service/audio/codecctl.h"
-namespace Service {
-namespace Audio {
+namespace Service::Audio {
void InstallInterfaces(SM::ServiceManager& service_manager) {
std::make_shared<AudOutU>()->InstallAsService(service_manager);
@@ -20,5 +19,4 @@ void InstallInterfaces(SM::ServiceManager& service_manager) {
std::make_shared<CodecCtl>()->InstallAsService(service_manager);
}
-} // namespace Audio
-} // namespace Service
+} // namespace Service::Audio
diff --git a/src/core/hle/service/audio/audio.h b/src/core/hle/service/audio/audio.h
index cbd56b2a8..95e5691f7 100644
--- a/src/core/hle/service/audio/audio.h
+++ b/src/core/hle/service/audio/audio.h
@@ -6,11 +6,9 @@
#include "core/hle/service/service.h"
-namespace Service {
-namespace Audio {
+namespace Service::Audio {
/// Registers all Audio services with the specified service manager.
void InstallInterfaces(SM::ServiceManager& service_manager);
-} // namespace Audio
-} // namespace Service
+} // namespace Service::Audio
diff --git a/src/core/hle/service/audio/audout_u.cpp b/src/core/hle/service/audio/audout_u.cpp
index 780a4e6e5..402eaa306 100644
--- a/src/core/hle/service/audio/audout_u.cpp
+++ b/src/core/hle/service/audio/audout_u.cpp
@@ -10,8 +10,7 @@
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/service/audio/audout_u.h"
-namespace Service {
-namespace Audio {
+namespace Service::Audio {
/// Switch sample rate frequency
constexpr u32 sample_rate{48000};
@@ -19,21 +18,24 @@ constexpr u32 sample_rate{48000};
/// to more audio channels (probably when Docked I guess)
constexpr u32 audio_channels{2};
/// TODO(st4rk): find a proper value for the audio_ticks
-constexpr u64 audio_ticks{static_cast<u64>(BASE_CLOCK_RATE / 500)};
+constexpr u64 audio_ticks{static_cast<u64>(CoreTiming::BASE_CLOCK_RATE / 500)};
class IAudioOut final : public ServiceFramework<IAudioOut> {
public:
IAudioOut() : ServiceFramework("IAudioOut"), audio_out_state(AudioState::Stopped) {
static const FunctionInfo functions[] = {
- {0x0, nullptr, "GetAudioOutState"},
- {0x1, &IAudioOut::StartAudioOut, "StartAudioOut"},
- {0x2, &IAudioOut::StopAudioOut, "StopAudioOut"},
- {0x3, &IAudioOut::AppendAudioOutBuffer_1, "AppendAudioOutBuffer_1"},
- {0x4, &IAudioOut::RegisterBufferEvent, "RegisterBufferEvent"},
- {0x5, &IAudioOut::GetReleasedAudioOutBuffer_1, "GetReleasedAudioOutBuffer_1"},
- {0x6, nullptr, "ContainsAudioOutBuffer"},
- {0x7, nullptr, "AppendAudioOutBuffer_2"},
- {0x8, nullptr, "GetReleasedAudioOutBuffer_2"},
+ {0, &IAudioOut::GetAudioOutState, "GetAudioOutState"},
+ {1, &IAudioOut::StartAudioOut, "StartAudioOut"},
+ {2, &IAudioOut::StopAudioOut, "StopAudioOut"},
+ {3, &IAudioOut::AppendAudioOutBuffer, "AppendAudioOutBuffer"},
+ {4, &IAudioOut::RegisterBufferEvent, "RegisterBufferEvent"},
+ {5, &IAudioOut::GetReleasedAudioOutBuffer, "GetReleasedAudioOutBuffer"},
+ {6, nullptr, "ContainsAudioOutBuffer"},
+ {7, nullptr, "AppendAudioOutBufferAuto"},
+ {8, nullptr, "GetReleasedAudioOutBufferAuto"},
+ {9, nullptr, "GetAudioOutBufferCount"},
+ {10, nullptr, "GetAudioOutPlayedSampleCount"},
+ {11, nullptr, "FlushAudioOutBuffers"},
};
RegisterHandlers(functions);
@@ -52,11 +54,20 @@ public:
CoreTiming::ScheduleEvent(audio_ticks, audio_event);
}
- ~IAudioOut() = default;
+ ~IAudioOut() {
+ CoreTiming::UnscheduleEvent(audio_event, 0);
+ }
private:
+ void GetAudioOutState(Kernel::HLERequestContext& ctx) {
+ NGLOG_DEBUG(Service_Audio, "called");
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(static_cast<u32>(audio_out_state));
+ }
+
void StartAudioOut(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_Audio, "(STUBBED) called");
+ NGLOG_WARNING(Service_Audio, "(STUBBED) called");
// Start audio
audio_out_state = AudioState::Started;
@@ -66,7 +77,7 @@ private:
}
void StopAudioOut(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_Audio, "(STUBBED) called");
+ NGLOG_WARNING(Service_Audio, "(STUBBED) called");
// Stop audio
audio_out_state = AudioState::Stopped;
@@ -78,15 +89,15 @@ private:
}
void RegisterBufferEvent(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_Audio, "(STUBBED) called");
+ NGLOG_WARNING(Service_Audio, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
rb.PushCopyObjects(buffer_event);
}
- void AppendAudioOutBuffer_1(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_Audio, "(STUBBED) called");
+ void AppendAudioOutBuffer(Kernel::HLERequestContext& ctx) {
+ NGLOG_WARNING(Service_Audio, "(STUBBED) called");
IPC::RequestParser rp{ctx};
const u64 key{rp.Pop<u64>()};
@@ -96,8 +107,8 @@ private:
rb.Push(RESULT_SUCCESS);
}
- void GetReleasedAudioOutBuffer_1(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_Audio, "(STUBBED) called");
+ void GetReleasedAudioOutBuffer(Kernel::HLERequestContext& ctx) {
+ NGLOG_WARNING(Service_Audio, "(STUBBED) called");
// TODO(st4rk): This is how libtransistor currently implements the
// GetReleasedAudioOutBuffer, it should return the key (a VAddr) to the app and this address
@@ -153,7 +164,7 @@ private:
};
void AudOutU::ListAudioOuts(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_Audio, "(STUBBED) called");
+ NGLOG_WARNING(Service_Audio, "(STUBBED) called");
IPC::RequestParser rp{ctx};
const std::string audio_interface = "AudioInterface";
@@ -169,7 +180,7 @@ void AudOutU::ListAudioOuts(Kernel::HLERequestContext& ctx) {
}
void AudOutU::OpenAudioOut(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_Audio, "(STUBBED) called");
+ NGLOG_WARNING(Service_Audio, "(STUBBED) called");
if (!audio_out_interface) {
audio_out_interface = std::make_shared<IAudioOut>();
@@ -185,12 +196,11 @@ void AudOutU::OpenAudioOut(Kernel::HLERequestContext& ctx) {
}
AudOutU::AudOutU() : ServiceFramework("audout:u") {
- static const FunctionInfo functions[] = {{0x00000000, &AudOutU::ListAudioOuts, "ListAudioOuts"},
- {0x00000001, &AudOutU::OpenAudioOut, "OpenAudioOut"},
- {0x00000002, nullptr, "Unknown2"},
- {0x00000003, nullptr, "Unknown3"}};
+ static const FunctionInfo functions[] = {{0, &AudOutU::ListAudioOuts, "ListAudioOuts"},
+ {1, &AudOutU::OpenAudioOut, "OpenAudioOut"},
+ {2, nullptr, "ListAudioOutsAuto"},
+ {3, nullptr, "OpenAudioOutAuto"}};
RegisterHandlers(functions);
}
-} // namespace Audio
-} // namespace Service
+} // namespace Service::Audio
diff --git a/src/core/hle/service/audio/audout_u.h b/src/core/hle/service/audio/audout_u.h
index 7fbce2225..1f9bb9bcf 100644
--- a/src/core/hle/service/audio/audout_u.h
+++ b/src/core/hle/service/audio/audout_u.h
@@ -10,8 +10,7 @@ namespace Kernel {
class HLERequestContext;
}
-namespace Service {
-namespace Audio {
+namespace Service::Audio {
class IAudioOut;
@@ -37,5 +36,4 @@ private:
};
};
-} // namespace Audio
-} // namespace Service
+} // namespace Service::Audio
diff --git a/src/core/hle/service/audio/audrec_u.cpp b/src/core/hle/service/audio/audrec_u.cpp
index f2626ec70..74909415c 100644
--- a/src/core/hle/service/audio/audrec_u.cpp
+++ b/src/core/hle/service/audio/audrec_u.cpp
@@ -7,20 +7,22 @@
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/service/audio/audrec_u.h"
-namespace Service {
-namespace Audio {
+namespace Service::Audio {
class IFinalOutputRecorder final : public ServiceFramework<IFinalOutputRecorder> {
public:
IFinalOutputRecorder() : ServiceFramework("IFinalOutputRecorder") {
static const FunctionInfo functions[] = {
- {0x0, nullptr, "GetFinalOutputRecorderState"},
- {0x1, nullptr, "StartFinalOutputRecorder"},
- {0x2, nullptr, "StopFinalOutputRecorder"},
- {0x3, nullptr, "AppendFinalOutputRecorderBuffer"},
- {0x4, nullptr, "RegisterBufferEvent"},
- {0x5, nullptr, "GetReleasedFinalOutputRecorderBuffer"},
- {0x6, nullptr, "ContainsFinalOutputRecorderBuffer"},
+ {0, nullptr, "GetFinalOutputRecorderState"},
+ {1, nullptr, "StartFinalOutputRecorder"},
+ {2, nullptr, "StopFinalOutputRecorder"},
+ {3, nullptr, "AppendFinalOutputRecorderBuffer"},
+ {4, nullptr, "RegisterBufferEvent"},
+ {5, nullptr, "GetReleasedFinalOutputRecorderBuffer"},
+ {6, nullptr, "ContainsFinalOutputRecorderBuffer"},
+ {7, nullptr, "Unknown"},
+ {8, nullptr, "AppendFinalOutputRecorderBufferAuto"},
+ {9, nullptr, "GetReleasedFinalOutputRecorderBufferAuto"},
};
RegisterHandlers(functions);
}
@@ -29,10 +31,9 @@ public:
AudRecU::AudRecU() : ServiceFramework("audrec:u") {
static const FunctionInfo functions[] = {
- {0x00000000, nullptr, "OpenFinalOutputRecorder"},
+ {0, nullptr, "OpenFinalOutputRecorder"},
};
RegisterHandlers(functions);
}
-} // namespace Audio
-} // namespace Service
+} // namespace Service::Audio
diff --git a/src/core/hle/service/audio/audrec_u.h b/src/core/hle/service/audio/audrec_u.h
index c31e412c1..46daa33a4 100644
--- a/src/core/hle/service/audio/audrec_u.h
+++ b/src/core/hle/service/audio/audrec_u.h
@@ -10,8 +10,7 @@ namespace Kernel {
class HLERequestContext;
}
-namespace Service {
-namespace Audio {
+namespace Service::Audio {
class AudRecU final : public ServiceFramework<AudRecU> {
public:
@@ -19,5 +18,4 @@ public:
~AudRecU() = default;
};
-} // namespace Audio
-} // namespace Service
+} // namespace Service::Audio
diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp
index dda135d18..6e8002bc9 100644
--- a/src/core/hle/service/audio/audren_u.cpp
+++ b/src/core/hle/service/audio/audren_u.cpp
@@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include "common/alignment.h"
#include "common/logging/log.h"
#include "core/core_timing.h"
#include "core/hle/ipc_helpers.h"
@@ -9,26 +10,27 @@
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/service/audio/audren_u.h"
-namespace Service {
-namespace Audio {
+namespace Service::Audio {
/// TODO(bunnei): Find a proper value for the audio_ticks
-constexpr u64 audio_ticks{static_cast<u64>(BASE_CLOCK_RATE / 200)};
+constexpr u64 audio_ticks{static_cast<u64>(CoreTiming::BASE_CLOCK_RATE / 200)};
class IAudioRenderer final : public ServiceFramework<IAudioRenderer> {
public:
IAudioRenderer() : ServiceFramework("IAudioRenderer") {
static const FunctionInfo functions[] = {
- {0x0, nullptr, "GetAudioRendererSampleRate"},
- {0x1, nullptr, "GetAudioRendererSampleCount"},
- {0x2, nullptr, "GetAudioRendererMixBufferCount"},
- {0x3, nullptr, "GetAudioRendererState"},
- {0x4, &IAudioRenderer::RequestUpdateAudioRenderer, "RequestUpdateAudioRenderer"},
- {0x5, &IAudioRenderer::StartAudioRenderer, "StartAudioRenderer"},
- {0x6, &IAudioRenderer::StopAudioRenderer, "StopAudioRenderer"},
- {0x7, &IAudioRenderer::QuerySystemEvent, "QuerySystemEvent"},
- {0x8, nullptr, "SetAudioRendererRenderingTimeLimit"},
- {0x9, nullptr, "GetAudioRendererRenderingTimeLimit"},
+ {0, nullptr, "GetAudioRendererSampleRate"},
+ {1, nullptr, "GetAudioRendererSampleCount"},
+ {2, nullptr, "GetAudioRendererMixBufferCount"},
+ {3, nullptr, "GetAudioRendererState"},
+ {4, &IAudioRenderer::RequestUpdateAudioRenderer, "RequestUpdateAudioRenderer"},
+ {5, &IAudioRenderer::StartAudioRenderer, "StartAudioRenderer"},
+ {6, &IAudioRenderer::StopAudioRenderer, "StopAudioRenderer"},
+ {7, &IAudioRenderer::QuerySystemEvent, "QuerySystemEvent"},
+ {8, nullptr, "SetAudioRendererRenderingTimeLimit"},
+ {9, nullptr, "GetAudioRendererRenderingTimeLimit"},
+ {10, nullptr, "RequestUpdateAudioRendererAuto"},
+ {11, nullptr, "ExecuteAudioRendererRendering"},
};
RegisterHandlers(functions);
@@ -45,7 +47,9 @@ public:
// Start the audio event
CoreTiming::ScheduleEvent(audio_ticks, audio_event);
}
- ~IAudioRenderer() = default;
+ ~IAudioRenderer() {
+ CoreTiming::UnscheduleEvent(audio_event, 0);
+ }
private:
void UpdateAudioCallback() {
@@ -53,16 +57,16 @@ private:
}
void RequestUpdateAudioRenderer(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_Audio, "%s", ctx.Description().c_str());
+ NGLOG_DEBUG(Service_Audio, "{}", ctx.Description());
AudioRendererResponseData response_data{};
response_data.section_0_size =
- response_data.state_entries.size() * sizeof(AudioRendererStateEntry);
- response_data.section_1_size = response_data.section_1.size();
- response_data.section_2_size = response_data.section_2.size();
- response_data.section_3_size = response_data.section_3.size();
- response_data.section_4_size = response_data.section_4.size();
- response_data.section_5_size = response_data.section_5.size();
+ static_cast<u32>(response_data.state_entries.size() * sizeof(AudioRendererStateEntry));
+ response_data.section_1_size = static_cast<u32>(response_data.section_1.size());
+ response_data.section_2_size = static_cast<u32>(response_data.section_2.size());
+ response_data.section_3_size = static_cast<u32>(response_data.section_3.size());
+ response_data.section_4_size = static_cast<u32>(response_data.section_4.size());
+ response_data.section_5_size = static_cast<u32>(response_data.section_5.size());
response_data.total_size = sizeof(AudioRendererResponseData);
for (unsigned i = 0; i < response_data.state_entries.size(); i++) {
@@ -76,7 +80,7 @@ private:
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_Audio, "(STUBBED) called");
+ NGLOG_WARNING(Service_Audio, "(STUBBED) called");
}
void StartAudioRenderer(Kernel::HLERequestContext& ctx) {
@@ -84,7 +88,7 @@ private:
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_Audio, "(STUBBED) called");
+ NGLOG_WARNING(Service_Audio, "(STUBBED) called");
}
void StopAudioRenderer(Kernel::HLERequestContext& ctx) {
@@ -92,7 +96,7 @@ private:
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_Audio, "(STUBBED) called");
+ NGLOG_WARNING(Service_Audio, "(STUBBED) called");
}
void QuerySystemEvent(Kernel::HLERequestContext& ctx) {
@@ -102,7 +106,7 @@ private:
rb.Push(RESULT_SUCCESS);
rb.PushCopyObjects(system_event);
- LOG_WARNING(Service_Audio, "(STUBBED) called");
+ NGLOG_WARNING(Service_Audio, "(STUBBED) called");
}
struct AudioRendererStateEntry {
@@ -149,12 +153,96 @@ private:
Kernel::SharedPtr<Kernel::Event> system_event;
};
+class IAudioDevice final : public ServiceFramework<IAudioDevice> {
+public:
+ IAudioDevice() : ServiceFramework("IAudioDevice") {
+ static const FunctionInfo functions[] = {
+ {0, &IAudioDevice::ListAudioDeviceName, "ListAudioDeviceName"},
+ {1, &IAudioDevice::SetAudioDeviceOutputVolume, "SetAudioDeviceOutputVolume"},
+ {2, nullptr, "GetAudioDeviceOutputVolume"},
+ {3, &IAudioDevice::GetActiveAudioDeviceName, "GetActiveAudioDeviceName"},
+ {4, &IAudioDevice::QueryAudioDeviceSystemEvent, "QueryAudioDeviceSystemEvent"},
+ {5, &IAudioDevice::GetActiveChannelCount, "GetActiveChannelCount"},
+ {6, &IAudioDevice::ListAudioDeviceName,
+ "ListAudioDeviceNameAuto"}, // TODO(ogniK): Confirm if autos are identical to non auto
+ {7, &IAudioDevice::SetAudioDeviceOutputVolume, "SetAudioDeviceOutputVolumeAuto"},
+ {8, nullptr, "GetAudioDeviceOutputVolumeAuto"},
+ {10, &IAudioDevice::GetActiveAudioDeviceName, "GetActiveAudioDeviceNameAuto"},
+ {11, nullptr, "QueryAudioDeviceInputEvent"},
+ {12, nullptr, "QueryAudioDeviceOutputEvent"},
+ };
+ RegisterHandlers(functions);
+
+ buffer_event =
+ Kernel::Event::Create(Kernel::ResetType::OneShot, "IAudioOutBufferReleasedEvent");
+ }
+
+private:
+ void ListAudioDeviceName(Kernel::HLERequestContext& ctx) {
+ NGLOG_WARNING(Service_Audio, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+
+ const std::string audio_interface = "AudioInterface";
+ ctx.WriteBuffer(audio_interface.c_str(), audio_interface.size());
+
+ IPC::ResponseBuilder rb = rp.MakeBuilder(3, 0, 0);
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(1);
+ }
+
+ void SetAudioDeviceOutputVolume(Kernel::HLERequestContext& ctx) {
+ NGLOG_WARNING(Service_Audio, "(STUBBED) called");
+
+ IPC::RequestParser rp{ctx};
+ f32 volume = static_cast<f32>(rp.Pop<u32>());
+
+ auto file_buffer = ctx.ReadBuffer();
+ auto end = std::find(file_buffer.begin(), file_buffer.end(), '\0');
+
+ IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0);
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void GetActiveAudioDeviceName(Kernel::HLERequestContext& ctx) {
+ NGLOG_WARNING(Service_Audio, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+
+ const std::string audio_interface = "AudioDevice";
+ ctx.WriteBuffer(audio_interface.c_str(), audio_interface.size());
+
+ IPC::ResponseBuilder rb = rp.MakeBuilder(3, 0, 0);
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(1);
+ }
+
+ void QueryAudioDeviceSystemEvent(Kernel::HLERequestContext& ctx) {
+ NGLOG_WARNING(Service_Audio, "(STUBBED) called");
+
+ buffer_event->Signal();
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushCopyObjects(buffer_event);
+ }
+
+ void GetActiveChannelCount(Kernel::HLERequestContext& ctx) {
+ NGLOG_WARNING(Service_Audio, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(1);
+ }
+
+ Kernel::SharedPtr<Kernel::Event> buffer_event;
+
+}; // namespace Audio
+
AudRenU::AudRenU() : ServiceFramework("audren:u") {
static const FunctionInfo functions[] = {
{0, &AudRenU::OpenAudioRenderer, "OpenAudioRenderer"},
{1, &AudRenU::GetAudioRendererWorkBufferSize, "GetAudioRendererWorkBufferSize"},
- {2, &AudRenU::GetAudioRenderersProcessMasterVolume, "GetAudioRenderersProcessMasterVolume"},
- {3, nullptr, "SetAudioRenderersProcessMasterVolume"},
+ {2, &AudRenU::GetAudioDevice, "GetAudioDevice"},
+ {3, nullptr, "OpenAudioRendererAuto"},
+ {4, nullptr, "GetAudioDeviceServiceWithRevisionInfo"},
};
RegisterHandlers(functions);
}
@@ -165,25 +253,85 @@ void AudRenU::OpenAudioRenderer(Kernel::HLERequestContext& ctx) {
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<Audio::IAudioRenderer>();
- LOG_DEBUG(Service_Audio, "called");
+ NGLOG_DEBUG(Service_Audio, "called");
}
void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ auto params = rp.PopRaw<WorkerBufferParameters>();
+
+ u64 buffer_sz = Common::AlignUp(4 * params.unknown8, 0x40);
+ buffer_sz += params.unknownC * 1024;
+ buffer_sz += 0x940 * (params.unknownC + 1);
+ buffer_sz += 0x3F0 * params.voice_count;
+ buffer_sz += Common::AlignUp(8 * (params.unknownC + 1), 0x10);
+ buffer_sz += Common::AlignUp(8 * params.voice_count, 0x10);
+ buffer_sz +=
+ Common::AlignUp((0x3C0 * (params.sink_count + params.unknownC) + 4 * params.sample_count) *
+ (params.unknown8 + 6),
+ 0x40);
+
+ if (IsFeatureSupported(AudioFeatures::Splitter, params.magic)) {
+ u32 count = params.unknownC + 1;
+ u64 node_count = Common::AlignUp(count, 0x40);
+ 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;
+ }
+ 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.magic)) {
+ buffer_sz += 0xE0 * params.unknown2c;
+ buffer_sz += 0x20 * params.splitter_count;
+ buffer_sz += Common::AlignUp(4 * params.unknown2c, 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.unknown1c >= 1) {
+ output_sz = Common::AlignUp(((16 * params.sink_count + 16 * params.effect_count +
+ 16 * params.voice_count + 16) +
+ 0x658) *
+ (params.unknown1c + 1) +
+ 0xc0,
+ 0x40) +
+ output_sz;
+ }
+ output_sz = Common::AlignUp(output_sz + 0x1807e, 0x1000);
+
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
- rb.Push<u64>(0x400);
+ rb.Push<u64>(output_sz);
- LOG_WARNING(Service_Audio, "(STUBBED) called");
+ NGLOG_DEBUG(Service_Audio, "called, buffer_size=0x{:X}", output_sz);
}
-void AudRenU::GetAudioRenderersProcessMasterVolume(Kernel::HLERequestContext& ctx) {
- IPC::ResponseBuilder rb{ctx, 3};
+void AudRenU::GetAudioDevice(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(100);
- LOG_WARNING(Service_Audio, "(STUBBED) called");
+ rb.PushIpcInterface<Audio::IAudioDevice>();
+
+ NGLOG_DEBUG(Service_Audio, "called");
+}
+
+bool AudRenU::IsFeatureSupported(AudioFeatures feature, u32_le revision) const {
+ u32_be version_num = (revision - Common::MakeMagic('R', 'E', 'V', '0')); // Byte swap
+ switch (feature) {
+ case AudioFeatures::Splitter:
+ return version_num >= 2;
+ default:
+ return false;
+ }
}
-} // namespace Audio
-} // namespace Service
+} // namespace Service::Audio
diff --git a/src/core/hle/service/audio/audren_u.h b/src/core/hle/service/audio/audren_u.h
index 939d353a9..fe53de4ce 100644
--- a/src/core/hle/service/audio/audren_u.h
+++ b/src/core/hle/service/audio/audren_u.h
@@ -10,8 +10,7 @@ namespace Kernel {
class HLERequestContext;
}
-namespace Service {
-namespace Audio {
+namespace Service::Audio {
class AudRenU final : public ServiceFramework<AudRenU> {
public:
@@ -21,8 +20,32 @@ public:
private:
void OpenAudioRenderer(Kernel::HLERequestContext& ctx);
void GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx);
- void GetAudioRenderersProcessMasterVolume(Kernel::HLERequestContext& ctx);
+ void GetAudioDevice(Kernel::HLERequestContext& ctx);
+
+ struct WorkerBufferParameters {
+ u32_le sample_rate;
+ u32_le sample_count;
+ u32_le unknown8;
+ u32_le unknownC;
+ u32_le voice_count;
+ u32_le sink_count;
+ u32_le effect_count;
+ u32_le unknown1c;
+ u8 unknown20;
+ u8 padding1[3];
+ u32_le splitter_count;
+ u32_le unknown2c;
+ u8 padding2[4];
+ u32_le magic;
+ };
+ static_assert(sizeof(WorkerBufferParameters) == 52,
+ "WorkerBufferParameters is an invalid size");
+
+ enum class AudioFeatures : u32 {
+ Splitter,
+ };
+
+ bool IsFeatureSupported(AudioFeatures feature, u32_le revision) const;
};
-} // namespace Audio
-} // namespace Service
+} // namespace Service::Audio
diff --git a/src/core/hle/service/audio/codecctl.cpp b/src/core/hle/service/audio/codecctl.cpp
index d2a7f4cd0..212c8d448 100644
--- a/src/core/hle/service/audio/codecctl.cpp
+++ b/src/core/hle/service/audio/codecctl.cpp
@@ -7,27 +7,25 @@
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/service/audio/codecctl.h"
-namespace Service {
-namespace Audio {
+namespace Service::Audio {
CodecCtl::CodecCtl() : ServiceFramework("codecctl") {
static const FunctionInfo functions[] = {
- {0x00000000, nullptr, "InitializeCodecController"},
- {0x00000001, nullptr, "FinalizeCodecController"},
- {0x00000002, nullptr, "SleepCodecController"},
- {0x00000003, nullptr, "WakeCodecController"},
- {0x00000004, nullptr, "SetCodecVolume"},
- {0x00000005, nullptr, "GetCodecVolumeMax"},
- {0x00000006, nullptr, "GetCodecVolumeMin"},
- {0x00000007, nullptr, "SetCodecActiveTarget"},
- {0x00000008, nullptr, "Unknown"},
- {0x00000009, nullptr, "BindCodecHeadphoneMicJackInterrupt"},
- {0x0000000A, nullptr, "IsCodecHeadphoneMicJackInserted"},
- {0x0000000B, nullptr, "ClearCodecHeadphoneMicJackInterrupt"},
- {0x0000000C, nullptr, "IsCodecDeviceRequested"},
+ {0, nullptr, "InitializeCodecController"},
+ {1, nullptr, "FinalizeCodecController"},
+ {2, nullptr, "SleepCodecController"},
+ {3, nullptr, "WakeCodecController"},
+ {4, nullptr, "SetCodecVolume"},
+ {5, nullptr, "GetCodecVolumeMax"},
+ {6, nullptr, "GetCodecVolumeMin"},
+ {7, nullptr, "SetCodecActiveTarget"},
+ {8, nullptr, "GetCodecActiveTarget"},
+ {9, nullptr, "BindCodecHeadphoneMicJackInterrupt"},
+ {10, nullptr, "IsCodecHeadphoneMicJackInserted"},
+ {11, nullptr, "ClearCodecHeadphoneMicJackInterrupt"},
+ {12, nullptr, "IsCodecDeviceRequested"},
};
RegisterHandlers(functions);
}
-} // namespace Audio
-} // namespace Service
+} // namespace Service::Audio
diff --git a/src/core/hle/service/audio/codecctl.h b/src/core/hle/service/audio/codecctl.h
index 1121ab0b1..d9ac29b67 100644
--- a/src/core/hle/service/audio/codecctl.h
+++ b/src/core/hle/service/audio/codecctl.h
@@ -10,8 +10,7 @@ namespace Kernel {
class HLERequestContext;
}
-namespace Service {
-namespace Audio {
+namespace Service::Audio {
class CodecCtl final : public ServiceFramework<CodecCtl> {
public:
@@ -19,5 +18,4 @@ public:
~CodecCtl() = default;
};
-} // namespace Audio
-} // namespace Service
+} // namespace Service::Audio
diff --git a/src/core/hle/service/bcat/bcat.cpp b/src/core/hle/service/bcat/bcat.cpp
new file mode 100644
index 000000000..20ce692dc
--- /dev/null
+++ b/src/core/hle/service/bcat/bcat.cpp
@@ -0,0 +1,16 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/bcat/bcat.h"
+
+namespace Service::BCAT {
+
+BCAT::BCAT(std::shared_ptr<Module> module, const char* name)
+ : Module::Interface(std::move(module), name) {
+ static const FunctionInfo functions[] = {
+ {0, &BCAT::CreateBcatService, "CreateBcatService"},
+ };
+ RegisterHandlers(functions);
+}
+} // namespace Service::BCAT
diff --git a/src/core/hle/service/bcat/bcat.h b/src/core/hle/service/bcat/bcat.h
new file mode 100644
index 000000000..6632996a0
--- /dev/null
+++ b/src/core/hle/service/bcat/bcat.h
@@ -0,0 +1,16 @@
+// 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/bcat/module.h"
+
+namespace Service::BCAT {
+
+class BCAT final : public Module::Interface {
+public:
+ explicit BCAT(std::shared_ptr<Module> module, const char* name);
+};
+
+} // namespace Service::BCAT
diff --git a/src/core/hle/service/bcat/module.cpp b/src/core/hle/service/bcat/module.cpp
new file mode 100644
index 000000000..52be9db22
--- /dev/null
+++ b/src/core/hle/service/bcat/module.cpp
@@ -0,0 +1,53 @@
+// Copyright 2018 yuzu emulator team
+// 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/bcat/bcat.h"
+#include "core/hle/service/bcat/module.h"
+
+namespace Service::BCAT {
+
+class IBcatService final : public ServiceFramework<IBcatService> {
+public:
+ IBcatService() : ServiceFramework("IBcatService") {
+ static const FunctionInfo functions[] = {
+ {10100, nullptr, "RequestSyncDeliveryCache"},
+ {10101, nullptr, "RequestSyncDeliveryCacheWithDirectoryName"},
+ {10200, nullptr, "CancelSyncDeliveryCacheRequest"},
+ {20100, nullptr, "RequestSyncDeliveryCacheWithApplicationId"},
+ {20101, nullptr, "RequestSyncDeliveryCacheWithApplicationIdAndDirectoryName"},
+ {30100, nullptr, "SetPassphrase"},
+ {30200, nullptr, "RegisterBackgroundDeliveryTask"},
+ {30201, nullptr, "UnregisterBackgroundDeliveryTask"},
+ {30202, nullptr, "BlockDeliveryTask"},
+ {30203, nullptr, "UnblockDeliveryTask"},
+ {90100, nullptr, "EnumerateBackgroundDeliveryTask"},
+ {90200, nullptr, "GetDeliveryList"},
+ {90201, nullptr, "ClearDeliveryCacheStorage"},
+ {90300, nullptr, "GetPushNotificationLog"},
+ };
+ RegisterHandlers(functions);
+ }
+};
+
+void Module::Interface::CreateBcatService(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IBcatService>();
+ NGLOG_DEBUG(Service_BCAT, "called");
+}
+
+Module::Interface::Interface(std::shared_ptr<Module> module, const char* name)
+ : ServiceFramework(name), module(std::move(module)) {}
+
+void InstallInterfaces(SM::ServiceManager& service_manager) {
+ auto module = std::make_shared<Module>();
+ std::make_shared<BCAT>(module, "bcat:a")->InstallAsService(service_manager);
+ std::make_shared<BCAT>(module, "bcat:m")->InstallAsService(service_manager);
+ std::make_shared<BCAT>(module, "bcat:u")->InstallAsService(service_manager);
+ std::make_shared<BCAT>(module, "bcat:s")->InstallAsService(service_manager);
+}
+
+} // namespace Service::BCAT
diff --git a/src/core/hle/service/bcat/module.h b/src/core/hle/service/bcat/module.h
new file mode 100644
index 000000000..8366fb877
--- /dev/null
+++ b/src/core/hle/service/bcat/module.h
@@ -0,0 +1,27 @@
+// 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/service.h"
+
+namespace Service::BCAT {
+
+class Module final {
+public:
+ class Interface : public ServiceFramework<Interface> {
+ public:
+ Interface(std::shared_ptr<Module> module, const char* name);
+
+ void CreateBcatService(Kernel::HLERequestContext& ctx);
+
+ protected:
+ std::shared_ptr<Module> module;
+ };
+};
+
+/// Registers all BCAT services with the specified service manager.
+void InstallInterfaces(SM::ServiceManager& service_manager);
+
+} // namespace Service::BCAT
diff --git a/src/core/hle/service/fatal/fatal.cpp b/src/core/hle/service/fatal/fatal.cpp
new file mode 100644
index 000000000..2d4282209
--- /dev/null
+++ b/src/core/hle/service/fatal/fatal.cpp
@@ -0,0 +1,36 @@
+// Copyright 2018 yuzu emulator team
+// 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/fatal/fatal.h"
+#include "core/hle/service/fatal/fatal_p.h"
+#include "core/hle/service/fatal/fatal_u.h"
+
+namespace Service::Fatal {
+
+Module::Interface::Interface(std::shared_ptr<Module> module, const char* name)
+ : ServiceFramework(name), module(std::move(module)) {}
+
+void Module::Interface::ThrowFatalWithPolicy(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp(ctx);
+ u32 error_code = rp.Pop<u32>();
+ NGLOG_WARNING(Service_Fatal, "(STUBBED) called, error_code=0x{:X}", error_code);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
+void Module::Interface::ThrowFatalWithCpuContext(Kernel::HLERequestContext& ctx) {
+ NGLOG_WARNING(Service_Fatal, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
+void InstallInterfaces(SM::ServiceManager& service_manager) {
+ auto module = std::make_shared<Module>();
+ std::make_shared<Fatal_P>(module)->InstallAsService(service_manager);
+ std::make_shared<Fatal_U>(module)->InstallAsService(service_manager);
+}
+
+} // namespace Service::Fatal
diff --git a/src/core/hle/service/fatal/fatal.h b/src/core/hle/service/fatal/fatal.h
new file mode 100644
index 000000000..5bd111a14
--- /dev/null
+++ b/src/core/hle/service/fatal/fatal.h
@@ -0,0 +1,27 @@
+// 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/service.h"
+
+namespace Service::Fatal {
+
+class Module final {
+public:
+ class Interface : public ServiceFramework<Interface> {
+ public:
+ Interface(std::shared_ptr<Module> module, const char* name);
+
+ void ThrowFatalWithPolicy(Kernel::HLERequestContext& ctx);
+ void ThrowFatalWithCpuContext(Kernel::HLERequestContext& ctx);
+
+ protected:
+ std::shared_ptr<Module> module;
+ };
+};
+
+void InstallInterfaces(SM::ServiceManager& service_manager);
+
+} // namespace Service::Fatal
diff --git a/src/core/hle/service/fatal/fatal_p.cpp b/src/core/hle/service/fatal/fatal_p.cpp
new file mode 100644
index 000000000..a5254ac2f
--- /dev/null
+++ b/src/core/hle/service/fatal/fatal_p.cpp
@@ -0,0 +1,12 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/fatal/fatal_p.h"
+
+namespace Service::Fatal {
+
+Fatal_P::Fatal_P(std::shared_ptr<Module> module)
+ : Module::Interface(std::move(module), "fatal:p") {}
+
+} // namespace Service::Fatal
diff --git a/src/core/hle/service/fatal/fatal_p.h b/src/core/hle/service/fatal/fatal_p.h
new file mode 100644
index 000000000..bfd8c8b74
--- /dev/null
+++ b/src/core/hle/service/fatal/fatal_p.h
@@ -0,0 +1,16 @@
+// 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/fatal/fatal.h"
+
+namespace Service::Fatal {
+
+class Fatal_P final : public Module::Interface {
+public:
+ explicit Fatal_P(std::shared_ptr<Module> module);
+};
+
+} // namespace Service::Fatal
diff --git a/src/core/hle/service/fatal/fatal_u.cpp b/src/core/hle/service/fatal/fatal_u.cpp
new file mode 100644
index 000000000..f0631329e
--- /dev/null
+++ b/src/core/hle/service/fatal/fatal_u.cpp
@@ -0,0 +1,18 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/fatal/fatal_u.h"
+
+namespace Service::Fatal {
+
+Fatal_U::Fatal_U(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "fatal:u") {
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "ThrowFatal"},
+ {1, &Fatal_U::ThrowFatalWithPolicy, "ThrowFatalWithPolicy"},
+ {2, &Fatal_U::ThrowFatalWithCpuContext, "ThrowFatalWithCpuContext"},
+ };
+ RegisterHandlers(functions);
+}
+
+} // namespace Service::Fatal
diff --git a/src/core/hle/service/fatal/fatal_u.h b/src/core/hle/service/fatal/fatal_u.h
new file mode 100644
index 000000000..9b1a9e97a
--- /dev/null
+++ b/src/core/hle/service/fatal/fatal_u.h
@@ -0,0 +1,16 @@
+// 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/fatal/fatal.h"
+
+namespace Service::Fatal {
+
+class Fatal_U final : public Module::Interface {
+public:
+ explicit Fatal_U(std::shared_ptr<Module> module);
+};
+
+} // namespace Service::Fatal
diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp
index 4b47548fd..68d1c90a5 100644
--- a/src/core/hle/service/filesystem/filesystem.cpp
+++ b/src/core/hle/service/filesystem/filesystem.cpp
@@ -3,12 +3,14 @@
// Refer to the license.txt file included.
#include <boost/container/flat_map.hpp>
+#include "common/file_util.h"
#include "core/file_sys/filesystem.h"
+#include "core/file_sys/savedata_factory.h"
+#include "core/file_sys/sdmc_factory.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/filesystem/fsp_srv.h"
-namespace Service {
-namespace FileSystem {
+namespace Service::FileSystem {
/**
* Map of registered file systems, identified by type. Once an file system is registered here, it
@@ -23,14 +25,14 @@ ResultCode RegisterFileSystem(std::unique_ptr<FileSys::FileSystemFactory>&& fact
ASSERT_MSG(inserted, "Tried to register more than one system with same id code");
auto& filesystem = result.first->second;
- LOG_DEBUG(Service_FS, "Registered file system %s with id code 0x%08X",
- filesystem->GetName().c_str(), static_cast<u32>(type));
+ NGLOG_DEBUG(Service_FS, "Registered file system {} with id code 0x{:08X}",
+ filesystem->GetName(), static_cast<u32>(type));
return RESULT_SUCCESS;
}
ResultVal<std::unique_ptr<FileSys::FileSystemBackend>> OpenFileSystem(Type type,
FileSys::Path& path) {
- LOG_TRACE(Service_FS, "Opening FileSystem with type=%d", type);
+ NGLOG_TRACE(Service_FS, "Opening FileSystem with type={}", static_cast<u32>(type));
auto itr = filesystem_map.find(type);
if (itr == filesystem_map.end()) {
@@ -41,14 +43,35 @@ ResultVal<std::unique_ptr<FileSys::FileSystemBackend>> OpenFileSystem(Type type,
return itr->second->Open(path);
}
-void UnregisterFileSystems() {
+ResultCode FormatFileSystem(Type type) {
+ NGLOG_TRACE(Service_FS, "Formatting FileSystem with type={}", static_cast<u32>(type));
+
+ auto itr = filesystem_map.find(type);
+ if (itr == filesystem_map.end()) {
+ // TODO(bunnei): Find a better error code for this
+ return ResultCode(-1);
+ }
+
+ FileSys::Path unused;
+ return itr->second->Format(unused);
+}
+
+void RegisterFileSystems() {
filesystem_map.clear();
+
+ std::string nand_directory = FileUtil::GetUserPath(D_NAND_IDX);
+ std::string sd_directory = FileUtil::GetUserPath(D_SDMC_IDX);
+
+ auto savedata = std::make_unique<FileSys::SaveData_Factory>(std::move(nand_directory));
+ RegisterFileSystem(std::move(savedata), Type::SaveData);
+
+ auto sdcard = std::make_unique<FileSys::SDMC_Factory>(std::move(sd_directory));
+ RegisterFileSystem(std::move(sdcard), Type::SDMC);
}
void InstallInterfaces(SM::ServiceManager& service_manager) {
- UnregisterFileSystems();
+ RegisterFileSystems();
std::make_shared<FSP_SRV>()->InstallAsService(service_manager);
}
-} // namespace FileSystem
-} // namespace Service
+} // namespace Service::FileSystem
diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h
index a674c9493..56d26146e 100644
--- a/src/core/hle/service/filesystem/filesystem.h
+++ b/src/core/hle/service/filesystem/filesystem.h
@@ -25,6 +25,8 @@ namespace FileSystem {
/// Supported FileSystem types
enum class Type {
RomFS = 1,
+ SaveData = 2,
+ SDMC = 3,
};
/**
@@ -43,6 +45,13 @@ ResultCode RegisterFileSystem(std::unique_ptr<FileSys::FileSystemFactory>&& fact
ResultVal<std::unique_ptr<FileSys::FileSystemBackend>> OpenFileSystem(Type type,
FileSys::Path& path);
+/**
+ * Formats a file system
+ * @param type Type of the file system to format
+ * @return ResultCode of the operation
+ */
+ResultCode FormatFileSystem(Type type);
+
/// Registers all Filesystem services with the specified service manager.
void InstallInterfaces(SM::ServiceManager& service_manager);
diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp
index 87a07e457..1cf97e876 100644
--- a/src/core/hle/service/filesystem/fsp_srv.cpp
+++ b/src/core/hle/service/filesystem/fsp_srv.cpp
@@ -2,8 +2,11 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <cinttypes>
#include "common/logging/log.h"
+#include "common/string_util.h"
#include "core/core.h"
+#include "core/file_sys/directory.h"
#include "core/file_sys/filesystem.h"
#include "core/file_sys/storage.h"
#include "core/hle/ipc_helpers.h"
@@ -12,8 +15,7 @@
#include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/filesystem/fsp_srv.h"
-namespace Service {
-namespace FileSystem {
+namespace Service::FileSystem {
class IStorage final : public ServiceFramework<IStorage> {
public:
@@ -21,7 +23,7 @@ public:
: ServiceFramework("IStorage"), backend(std::move(backend)) {
static const FunctionInfo functions[] = {
{0, &IStorage::Read, "Read"}, {1, nullptr, "Write"}, {2, nullptr, "Flush"},
- {3, nullptr, "SetSize"}, {4, nullptr, "GetSize"},
+ {3, nullptr, "SetSize"}, {4, nullptr, "GetSize"}, {5, nullptr, "OperateRange"},
};
RegisterHandlers(functions);
}
@@ -34,7 +36,7 @@ private:
const s64 offset = rp.Pop<s64>();
const s64 length = rp.Pop<s64>();
- LOG_DEBUG(Service_FS, "called, offset=0x%llx, length=0x%llx", offset, length);
+ NGLOG_DEBUG(Service_FS, "called, offset=0x{:X}, length={}", offset, length);
// Error checking
if (length < 0) {
@@ -65,14 +67,421 @@ private:
}
};
+class IFile final : public ServiceFramework<IFile> {
+public:
+ explicit IFile(std::unique_ptr<FileSys::StorageBackend>&& backend)
+ : ServiceFramework("IFile"), backend(std::move(backend)) {
+ static const FunctionInfo functions[] = {
+ {0, &IFile::Read, "Read"}, {1, &IFile::Write, "Write"},
+ {2, &IFile::Flush, "Flush"}, {3, &IFile::SetSize, "SetSize"},
+ {4, &IFile::GetSize, "GetSize"}, {5, nullptr, "OperateRange"},
+ };
+ RegisterHandlers(functions);
+ }
+
+private:
+ std::unique_ptr<FileSys::StorageBackend> backend;
+
+ void Read(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const u64 unk = rp.Pop<u64>();
+ const s64 offset = rp.Pop<s64>();
+ const s64 length = rp.Pop<s64>();
+
+ NGLOG_DEBUG(Service_FS, "called, offset=0x{:X}, length={}", offset, length);
+
+ // Error checking
+ if (length < 0) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultCode(ErrorModule::FS, ErrorDescription::InvalidLength));
+ return;
+ }
+ if (offset < 0) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultCode(ErrorModule::FS, ErrorDescription::InvalidOffset));
+ return;
+ }
+
+ // Read the data from the Storage backend
+ std::vector<u8> output(length);
+ ResultVal<size_t> res = backend->Read(offset, length, output.data());
+ if (res.Failed()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(res.Code());
+ return;
+ }
+
+ // Write the data to memory
+ ctx.WriteBuffer(output);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(static_cast<u64>(*res));
+ }
+
+ void Write(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const u64 unk = rp.Pop<u64>();
+ const s64 offset = rp.Pop<s64>();
+ const s64 length = rp.Pop<s64>();
+
+ NGLOG_DEBUG(Service_FS, "called, offset=0x{:X}, length={}", offset, length);
+
+ // Error checking
+ if (length < 0) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultCode(ErrorModule::FS, ErrorDescription::InvalidLength));
+ return;
+ }
+ if (offset < 0) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultCode(ErrorModule::FS, ErrorDescription::InvalidOffset));
+ return;
+ }
+
+ // Write the data to the Storage backend
+ std::vector<u8> data = ctx.ReadBuffer();
+ ResultVal<size_t> res = backend->Write(offset, length, true, data.data());
+ if (res.Failed()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(res.Code());
+ return;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void Flush(Kernel::HLERequestContext& ctx) {
+ NGLOG_DEBUG(Service_FS, "called");
+ backend->Flush();
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void SetSize(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const u64 size = rp.Pop<u64>();
+ backend->SetSize(size);
+ NGLOG_DEBUG(Service_FS, "called, size={}", size);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void GetSize(Kernel::HLERequestContext& ctx) {
+ const u64 size = backend->GetSize();
+ NGLOG_DEBUG(Service_FS, "called, size={}", size);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u64>(size);
+ }
+};
+
+class IDirectory final : public ServiceFramework<IDirectory> {
+public:
+ explicit IDirectory(std::unique_ptr<FileSys::DirectoryBackend>&& backend)
+ : ServiceFramework("IDirectory"), backend(std::move(backend)) {
+ static const FunctionInfo functions[] = {
+ {0, &IDirectory::Read, "Read"},
+ {1, &IDirectory::GetEntryCount, "GetEntryCount"},
+ };
+ RegisterHandlers(functions);
+ }
+
+private:
+ std::unique_ptr<FileSys::DirectoryBackend> backend;
+
+ void Read(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const u64 unk = rp.Pop<u64>();
+
+ NGLOG_DEBUG(Service_FS, "called, unk=0x{:X}", unk);
+
+ // Calculate how many entries we can fit in the output buffer
+ u64 count_entries = ctx.GetWriteBufferSize() / sizeof(FileSys::Entry);
+
+ // Read the data from the Directory backend
+ std::vector<FileSys::Entry> entries(count_entries);
+ u64 read_entries = backend->Read(count_entries, entries.data());
+
+ // Convert the data into a byte array
+ std::vector<u8> output(entries.size() * sizeof(FileSys::Entry));
+ std::memcpy(output.data(), entries.data(), output.size());
+
+ // Write the data to memory
+ ctx.WriteBuffer(output);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(read_entries);
+ }
+
+ void GetEntryCount(Kernel::HLERequestContext& ctx) {
+ NGLOG_DEBUG(Service_FS, "called");
+
+ u64 count = backend->GetEntryCount();
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(count);
+ }
+};
+
+class IFileSystem final : public ServiceFramework<IFileSystem> {
+public:
+ explicit IFileSystem(std::unique_ptr<FileSys::FileSystemBackend>&& backend)
+ : ServiceFramework("IFileSystem"), backend(std::move(backend)) {
+ static const FunctionInfo functions[] = {
+ {0, &IFileSystem::CreateFile, "CreateFile"},
+ {1, &IFileSystem::DeleteFile, "DeleteFile"},
+ {2, &IFileSystem::CreateDirectory, "CreateDirectory"},
+ {3, nullptr, "DeleteDirectory"},
+ {4, nullptr, "DeleteDirectoryRecursively"},
+ {5, &IFileSystem::RenameFile, "RenameFile"},
+ {6, nullptr, "RenameDirectory"},
+ {7, &IFileSystem::GetEntryType, "GetEntryType"},
+ {8, &IFileSystem::OpenFile, "OpenFile"},
+ {9, &IFileSystem::OpenDirectory, "OpenDirectory"},
+ {10, &IFileSystem::Commit, "Commit"},
+ {11, nullptr, "GetFreeSpaceSize"},
+ {12, nullptr, "GetTotalSpaceSize"},
+ {13, nullptr, "CleanDirectoryRecursively"},
+ {14, nullptr, "GetFileTimeStampRaw"},
+ {15, nullptr, "QueryEntry"},
+ };
+ RegisterHandlers(functions);
+ }
+
+ void CreateFile(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ auto file_buffer = ctx.ReadBuffer();
+ std::string name = Common::StringFromBuffer(file_buffer);
+
+ u64 mode = rp.Pop<u64>();
+ u32 size = rp.Pop<u32>();
+
+ NGLOG_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);
+
+ NGLOG_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);
+
+ NGLOG_DEBUG(Service_FS, "called directory {}", name);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(backend->CreateDirectory(name));
+ }
+
+ 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);
+
+ buffer.resize(ctx.BufferDescriptorX()[1].Size());
+ Memory::ReadBlock(ctx.BufferDescriptorX()[1].Address(), buffer.data(), buffer.size());
+ std::string dst_name = Common::StringFromBuffer(buffer);
+
+ NGLOG_DEBUG(Service_FS, "called file '{}' to file '{}'", src_name, dst_name);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(backend->RenameFile(src_name, dst_name));
+ }
+
+ void OpenFile(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ auto file_buffer = ctx.ReadBuffer();
+ std::string name = Common::StringFromBuffer(file_buffer);
+
+ auto mode = static_cast<FileSys::Mode>(rp.Pop<u32>());
+
+ NGLOG_DEBUG(Service_FS, "called file {} mode {}", name, static_cast<u32>(mode));
+
+ auto result = backend->OpenFile(name, mode);
+ if (result.Failed()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result.Code());
+ return;
+ }
+
+ auto file = std::move(result.Unwrap());
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IFile>(std::move(file));
+ }
+
+ void OpenDirectory(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ auto file_buffer = ctx.ReadBuffer();
+ std::string name = Common::StringFromBuffer(file_buffer);
+
+ // TODO(Subv): Implement this filter.
+ u32 filter_flags = rp.Pop<u32>();
+
+ NGLOG_DEBUG(Service_FS, "called directory {} filter {}", name, filter_flags);
+
+ auto result = backend->OpenDirectory(name);
+ if (result.Failed()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result.Code());
+ return;
+ }
+
+ auto directory = std::move(result.Unwrap());
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IDirectory>(std::move(directory));
+ }
+
+ void GetEntryType(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ auto file_buffer = ctx.ReadBuffer();
+ std::string name = Common::StringFromBuffer(file_buffer);
+
+ NGLOG_DEBUG(Service_FS, "called file {}", name);
+
+ auto result = backend->GetEntryType(name);
+ if (result.Failed()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result.Code());
+ return;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(static_cast<u32>(*result));
+ }
+
+ void Commit(Kernel::HLERequestContext& ctx) {
+ NGLOG_WARNING(Service_FS, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+private:
+ std::unique_ptr<FileSys::FileSystemBackend> backend;
+};
+
FSP_SRV::FSP_SRV() : ServiceFramework("fsp-srv") {
static const FunctionInfo functions[] = {
- {1, &FSP_SRV::Initalize, "Initalize"},
+ {0, nullptr, "MountContent"},
+ {1, &FSP_SRV::Initialize, "Initialize"},
+ {2, nullptr, "OpenDataFileSystemByCurrentProcess"},
+ {7, nullptr, "OpenFileSystemWithPatch"},
+ {8, nullptr, "OpenFileSystemWithId"},
+ {9, nullptr, "OpenDataFileSystemByApplicationId"},
+ {11, nullptr, "OpenBisFileSystem"},
+ {12, nullptr, "OpenBisStorage"},
+ {13, nullptr, "InvalidateBisCache"},
+ {17, nullptr, "OpenHostFileSystem"},
{18, &FSP_SRV::MountSdCard, "MountSdCard"},
+ {19, nullptr, "FormatSdCardFileSystem"},
+ {21, nullptr, "DeleteSaveDataFileSystem"},
+ {22, &FSP_SRV::CreateSaveData, "CreateSaveData"},
+ {23, nullptr, "CreateSaveDataFileSystemBySystemSaveDataId"},
+ {24, nullptr, "RegisterSaveDataFileSystemAtomicDeletion"},
+ {25, nullptr, "DeleteSaveDataFileSystemBySaveDataSpaceId"},
+ {26, nullptr, "FormatSdCardDryRun"},
+ {27, nullptr, "IsExFatSupported"},
+ {28, nullptr, "DeleteSaveDataFileSystemBySaveDataAttribute"},
+ {30, nullptr, "OpenGameCardStorage"},
+ {31, nullptr, "OpenGameCardFileSystem"},
+ {32, nullptr, "ExtendSaveDataFileSystem"},
+ {33, nullptr, "DeleteCacheStorage"},
+ {34, nullptr, "GetCacheStorageSize"},
+ {51, &FSP_SRV::MountSaveData, "MountSaveData"},
+ {52, nullptr, "OpenSaveDataFileSystemBySystemSaveDataId"},
+ {53, nullptr, "OpenReadOnlySaveDataFileSystem"},
+ {57, nullptr, "ReadSaveDataFileSystemExtraDataBySaveDataSpaceId"},
+ {58, nullptr, "ReadSaveDataFileSystemExtraData"},
+ {59, nullptr, "WriteSaveDataFileSystemExtraData"},
+ {60, nullptr, "OpenSaveDataInfoReader"},
+ {61, nullptr, "OpenSaveDataInfoReaderBySaveDataSpaceId"},
+ {62, nullptr, "OpenCacheStorageList"},
+ {64, nullptr, "OpenSaveDataInternalStorageFileSystem"},
+ {65, nullptr, "UpdateSaveDataMacForDebug"},
+ {66, nullptr, "WriteSaveDataFileSystemExtraData2"},
+ {80, nullptr, "OpenSaveDataMetaFile"},
+ {81, nullptr, "OpenSaveDataTransferManager"},
+ {82, nullptr, "OpenSaveDataTransferManagerVersion2"},
+ {100, nullptr, "OpenImageDirectoryFileSystem"},
+ {110, nullptr, "OpenContentStorageFileSystem"},
{200, &FSP_SRV::OpenDataStorageByCurrentProcess, "OpenDataStorageByCurrentProcess"},
+ {201, nullptr, "OpenDataStorageByProgramId"},
{202, nullptr, "OpenDataStorageByDataId"},
{203, &FSP_SRV::OpenRomStorage, "OpenRomStorage"},
+ {400, nullptr, "OpenDeviceOperator"},
+ {500, nullptr, "OpenSdCardDetectionEventNotifier"},
+ {501, nullptr, "OpenGameCardDetectionEventNotifier"},
+ {510, nullptr, "OpenSystemDataUpdateEventNotifier"},
+ {511, nullptr, "NotifySystemDataUpdateEvent"},
+ {600, nullptr, "SetCurrentPosixTime"},
+ {601, nullptr, "QuerySaveDataTotalSize"},
+ {602, nullptr, "VerifySaveDataFileSystem"},
+ {603, nullptr, "CorruptSaveDataFileSystem"},
+ {604, nullptr, "CreatePaddingFile"},
+ {605, nullptr, "DeleteAllPaddingFiles"},
+ {606, nullptr, "GetRightsId"},
+ {607, nullptr, "RegisterExternalKey"},
+ {608, nullptr, "UnregisterAllExternalKey"},
+ {609, nullptr, "GetRightsIdByPath"},
+ {610, nullptr, "GetRightsIdAndKeyGenerationByPath"},
+ {611, nullptr, "SetCurrentPosixTimeWithTimeDifference"},
+ {612, nullptr, "GetFreeSpaceSizeForSaveData"},
+ {613, nullptr, "VerifySaveDataFileSystemBySaveDataSpaceId"},
+ {614, nullptr, "CorruptSaveDataFileSystemBySaveDataSpaceId"},
+ {615, nullptr, "QuerySaveDataInternalStorageTotalSize"},
+ {620, nullptr, "SetSdCardEncryptionSeed"},
+ {630, nullptr, "SetSdCardAccessibility"},
+ {631, nullptr, "IsSdCardAccessible"},
+ {640, nullptr, "IsSignedSystemPartitionOnSdCardValid"},
+ {700, nullptr, "OpenAccessFailureResolver"},
+ {701, nullptr, "GetAccessFailureDetectionEvent"},
+ {702, nullptr, "IsAccessFailureDetected"},
+ {710, nullptr, "ResolveAccessFailure"},
+ {720, nullptr, "AbandonAccessFailure"},
+ {800, nullptr, "GetAndClearFileSystemProxyErrorInfo"},
+ {1000, nullptr, "SetBisRootForHost"},
+ {1001, nullptr, "SetSaveDataSize"},
+ {1002, nullptr, "SetSaveDataRootPath"},
+ {1003, nullptr, "DisableAutoSaveDataCreation"},
+ {1004, nullptr, "SetGlobalAccessLogMode"},
{1005, &FSP_SRV::GetGlobalAccessLogMode, "GetGlobalAccessLogMode"},
+ {1006, nullptr, "OutputAccessLogToSdCard"},
+ {1007, nullptr, "RegisterUpdatePartition"},
+ {1008, nullptr, "OpenRegisteredUpdatePartition"},
+ {1009, nullptr, "GetAndClearMemoryReportInfo"},
+ {1100, nullptr, "OverrideSaveDataTransferTokenSignVerificationKey"},
};
RegisterHandlers(functions);
}
@@ -88,22 +497,50 @@ void FSP_SRV::TryLoadRomFS() {
}
}
-void FSP_SRV::Initalize(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_FS, "(STUBBED) called");
+void FSP_SRV::Initialize(Kernel::HLERequestContext& ctx) {
+ NGLOG_WARNING(Service_FS, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void FSP_SRV::MountSdCard(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_FS, "(STUBBED) called");
+ NGLOG_DEBUG(Service_FS, "called");
+
+ FileSys::Path unused;
+ auto filesystem = OpenFileSystem(Type::SDMC, unused).Unwrap();
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IFileSystem>(std::move(filesystem));
+}
+
+void FSP_SRV::CreateSaveData(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ auto save_struct = rp.PopRaw<std::array<u8, 0x40>>();
+ auto save_create_struct = rp.PopRaw<std::array<u8, 0x40>>();
+ u128 uid = rp.PopRaw<u128>();
+
+ NGLOG_WARNING(Service_FS, "(STUBBED) called uid = {:016X}{:016X}", uid[1], uid[0]);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
+void FSP_SRV::MountSaveData(Kernel::HLERequestContext& ctx) {
+ NGLOG_WARNING(Service_FS, "(STUBBED) called");
+
+ FileSys::Path unused;
+ auto filesystem = OpenFileSystem(Type::SaveData, unused).Unwrap();
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IFileSystem>(std::move(filesystem));
+}
+
void FSP_SRV::GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_FS, "(STUBBED) called");
+ NGLOG_WARNING(Service_FS, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
@@ -111,12 +548,12 @@ void FSP_SRV::GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) {
}
void FSP_SRV::OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_FS, "called");
+ NGLOG_DEBUG(Service_FS, "called");
TryLoadRomFS();
if (!romfs) {
// TODO (bunnei): Find the right error code to use here
- LOG_CRITICAL(Service_FS, "no file system interface available!");
+ NGLOG_CRITICAL(Service_FS, "no file system interface available!");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultCode(-1));
return;
@@ -125,7 +562,7 @@ void FSP_SRV::OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx) {
// Attempt to open a StorageBackend interface to the RomFS
auto storage = romfs->OpenFile({}, {});
if (storage.Failed()) {
- LOG_CRITICAL(Service_FS, "no storage interface available!");
+ NGLOG_CRITICAL(Service_FS, "no storage interface available!");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(storage.Code());
return;
@@ -137,9 +574,8 @@ void FSP_SRV::OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx) {
}
void FSP_SRV::OpenRomStorage(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_FS, "(STUBBED) called, using OpenDataStorageByCurrentProcess");
+ NGLOG_WARNING(Service_FS, "(STUBBED) called, using OpenDataStorageByCurrentProcess");
OpenDataStorageByCurrentProcess(ctx);
}
-} // namespace FileSystem
-} // namespace Service
+} // namespace Service::FileSystem
diff --git a/src/core/hle/service/filesystem/fsp_srv.h b/src/core/hle/service/filesystem/fsp_srv.h
index 56afc4b90..acb78fac1 100644
--- a/src/core/hle/service/filesystem/fsp_srv.h
+++ b/src/core/hle/service/filesystem/fsp_srv.h
@@ -11,8 +11,7 @@ namespace FileSys {
class FileSystemBackend;
}
-namespace Service {
-namespace FileSystem {
+namespace Service::FileSystem {
class FSP_SRV final : public ServiceFramework<FSP_SRV> {
public:
@@ -22,8 +21,10 @@ public:
private:
void TryLoadRomFS();
- void Initalize(Kernel::HLERequestContext& ctx);
+ void Initialize(Kernel::HLERequestContext& ctx);
void MountSdCard(Kernel::HLERequestContext& ctx);
+ void CreateSaveData(Kernel::HLERequestContext& ctx);
+ void MountSaveData(Kernel::HLERequestContext& ctx);
void GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx);
void OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx);
void OpenRomStorage(Kernel::HLERequestContext& ctx);
@@ -31,5 +32,4 @@ private:
std::unique_ptr<FileSys::FileSystemBackend> romfs;
};
-} // namespace FileSystem
-} // namespace Service
+} // namespace Service::FileSystem
diff --git a/src/core/hle/service/friend/friend.cpp b/src/core/hle/service/friend/friend.cpp
index 26593bb0c..94d9fbf25 100644
--- a/src/core/hle/service/friend/friend.cpp
+++ b/src/core/hle/service/friend/friend.cpp
@@ -6,14 +6,14 @@
#include "core/hle/ipc_helpers.h"
#include "core/hle/service/friend/friend.h"
#include "core/hle/service/friend/friend_a.h"
+#include "core/hle/service/friend/friend_u.h"
-namespace Service {
-namespace Friend {
+namespace Service::Friend {
-void Module::Interface::Unknown(Kernel::HLERequestContext& ctx) {
+void Module::Interface::CreateFriendService(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_Friend, "(STUBBED) called");
+ NGLOG_WARNING(Service_Friend, "(STUBBED) called");
}
Module::Interface::Interface(std::shared_ptr<Module> module, const char* name)
@@ -22,7 +22,7 @@ Module::Interface::Interface(std::shared_ptr<Module> module, const char* name)
void InstallInterfaces(SM::ServiceManager& service_manager) {
auto module = std::make_shared<Module>();
std::make_shared<Friend_A>(module)->InstallAsService(service_manager);
+ std::make_shared<Friend_U>(module)->InstallAsService(service_manager);
}
-} // namespace Friend
-} // namespace Service
+} // namespace Service::Friend
diff --git a/src/core/hle/service/friend/friend.h b/src/core/hle/service/friend/friend.h
index ffa498397..4b72115c0 100644
--- a/src/core/hle/service/friend/friend.h
+++ b/src/core/hle/service/friend/friend.h
@@ -6,8 +6,7 @@
#include "core/hle/service/service.h"
-namespace Service {
-namespace Friend {
+namespace Service::Friend {
class Module final {
public:
@@ -15,7 +14,7 @@ public:
public:
Interface(std::shared_ptr<Module> module, const char* name);
- void Unknown(Kernel::HLERequestContext& ctx);
+ void CreateFriendService(Kernel::HLERequestContext& ctx);
protected:
std::shared_ptr<Module> module;
@@ -25,5 +24,4 @@ public:
/// Registers all Friend services with the specified service manager.
void InstallInterfaces(SM::ServiceManager& service_manager);
-} // namespace Friend
-} // namespace Service
+} // namespace Service::Friend
diff --git a/src/core/hle/service/friend/friend_a.cpp b/src/core/hle/service/friend/friend_a.cpp
index e1f2397c2..a2cc81926 100644
--- a/src/core/hle/service/friend/friend_a.cpp
+++ b/src/core/hle/service/friend/friend_a.cpp
@@ -4,16 +4,15 @@
#include "core/hle/service/friend/friend_a.h"
-namespace Service {
-namespace Friend {
+namespace Service::Friend {
Friend_A::Friend_A(std::shared_ptr<Module> module)
: Module::Interface(std::move(module), "friend:a") {
static const FunctionInfo functions[] = {
- {0, &Friend_A::Unknown, "Unknown"},
+ {0, &Friend_A::CreateFriendService, "CreateFriendService"},
+ {1, nullptr, "CreateNotificationService"},
};
RegisterHandlers(functions);
}
-} // namespace Friend
-} // namespace Service
+} // namespace Service::Friend
diff --git a/src/core/hle/service/friend/friend_a.h b/src/core/hle/service/friend/friend_a.h
index 68fa58297..81257583b 100644
--- a/src/core/hle/service/friend/friend_a.h
+++ b/src/core/hle/service/friend/friend_a.h
@@ -6,13 +6,11 @@
#include "core/hle/service/friend/friend.h"
-namespace Service {
-namespace Friend {
+namespace Service::Friend {
class Friend_A final : public Module::Interface {
public:
explicit Friend_A(std::shared_ptr<Module> module);
};
-} // namespace Friend
-} // namespace Service
+} // namespace Service::Friend
diff --git a/src/core/hle/service/friend/friend_u.cpp b/src/core/hle/service/friend/friend_u.cpp
new file mode 100644
index 000000000..90b30883f
--- /dev/null
+++ b/src/core/hle/service/friend/friend_u.cpp
@@ -0,0 +1,18 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/friend/friend_u.h"
+
+namespace Service::Friend {
+
+Friend_U::Friend_U(std::shared_ptr<Module> module)
+ : Module::Interface(std::move(module), "friend:u") {
+ static const FunctionInfo functions[] = {
+ {0, &Friend_U::CreateFriendService, "CreateFriendService"},
+ {1, nullptr, "CreateNotificationService"},
+ };
+ RegisterHandlers(functions);
+}
+
+} // namespace Service::Friend
diff --git a/src/core/hle/service/friend/friend_u.h b/src/core/hle/service/friend/friend_u.h
new file mode 100644
index 000000000..0d953d807
--- /dev/null
+++ b/src/core/hle/service/friend/friend_u.h
@@ -0,0 +1,16 @@
+// 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/friend/friend.h"
+
+namespace Service::Friend {
+
+class Friend_U final : public Module::Interface {
+public:
+ explicit Friend_U(std::shared_ptr<Module> module);
+};
+
+} // namespace Service::Friend
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index dacd1862d..85ca4bf06 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -14,14 +14,13 @@
#include "core/hle/service/hid/hid.h"
#include "core/hle/service/service.h"
-namespace Service {
-namespace HID {
+namespace Service::HID {
// Updating period for each HID device.
// TODO(shinyquagsire23): These need better values.
-constexpr u64 pad_update_ticks = BASE_CLOCK_RATE / 10000;
-constexpr u64 accelerometer_update_ticks = BASE_CLOCK_RATE / 10000;
-constexpr u64 gyroscope_update_ticks = BASE_CLOCK_RATE / 10000;
+constexpr u64 pad_update_ticks = CoreTiming::BASE_CLOCK_RATE / 10000;
+constexpr u64 accelerometer_update_ticks = CoreTiming::BASE_CLOCK_RATE / 10000;
+constexpr u64 gyroscope_update_ticks = CoreTiming::BASE_CLOCK_RATE / 10000;
class IAppletResource final : public ServiceFramework<IAppletResource> {
public:
@@ -45,29 +44,37 @@ public:
CoreTiming::ScheduleEvent(pad_update_ticks, pad_update_event);
}
+ ~IAppletResource() {
+ CoreTiming::UnscheduleEvent(pad_update_event, 0);
+ }
+
private:
void GetSharedMemoryHandle(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
rb.PushCopyObjects(shared_mem);
- LOG_DEBUG(Service_HID, "called");
+ NGLOG_DEBUG(Service_HID, "called");
}
void LoadInputDevices() {
std::transform(Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_BEGIN,
Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_END,
buttons.begin(), Input::CreateDevice<Input::ButtonDevice>);
- // TODO(shinyquagsire23): sticks, gyro, touch, mouse, keyboard
+ std::transform(Settings::values.analogs.begin() + Settings::NativeAnalog::STICK_HID_BEGIN,
+ Settings::values.analogs.begin() + Settings::NativeAnalog::STICK_HID_END,
+ sticks.begin(), Input::CreateDevice<Input::AnalogDevice>);
+ // TODO(shinyquagsire23): gyro, touch, mouse, keyboard
}
void UpdatePadCallback(u64 userdata, int cycles_late) {
- SharedMemory* mem = reinterpret_cast<SharedMemory*>(shared_mem->GetPointer());
+ SharedMemory mem{};
+ std::memcpy(&mem, shared_mem->GetPointer(), sizeof(SharedMemory));
if (is_device_reload_pending.exchange(false))
LoadInputDevices();
// Set up controllers as neon red+blue Joy-Con attached to console
- ControllerHeader& controller_header = mem->controllers[Controller_Handheld].header;
+ ControllerHeader& controller_header = mem.controllers[Controller_Handheld].header;
controller_header.type = ControllerType_Handheld | ControllerType_JoyconPair;
controller_header.single_colors_descriptor = ColorDesc_ColorsNonexistent;
controller_header.right_color_body = JOYCON_BODY_NEON_RED;
@@ -75,67 +82,157 @@ private:
controller_header.left_color_body = JOYCON_BODY_NEON_BLUE;
controller_header.left_color_buttons = JOYCON_BUTTONS_NEON_BLUE;
- for (int layoutIdx = 0; layoutIdx < HID_NUM_LAYOUTS; layoutIdx++) {
- ControllerLayout& layout = mem->controllers[Controller_Handheld].layouts[layoutIdx];
- layout.header.num_entries = HID_NUM_ENTRIES;
- layout.header.max_entry_index = HID_NUM_ENTRIES - 1;
-
- // HID shared memory stores the state of the past 17 samples in a circlular buffer,
- // each with a timestamp in number of samples since boot.
- layout.header.timestamp_ticks = CoreTiming::GetTicks();
- layout.header.latest_entry = (layout.header.latest_entry + 1) % HID_NUM_ENTRIES;
-
- ControllerInputEntry& entry = layout.entries[layout.header.latest_entry];
- entry.connection_state = ConnectionState_Connected | ConnectionState_Wired;
- entry.timestamp++;
- entry.timestamp_2++; // TODO(shinyquagsire23): Is this always identical to timestamp?
-
- // TODO(shinyquagsire23): Set up some LUTs for each layout mapping in the future?
- // For now everything is just the default handheld layout, but split Joy-Con will
- // rotate the face buttons and directions for certain layouts.
- ControllerPadState& state = entry.buttons;
- using namespace Settings::NativeButton;
- state.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus());
- state.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus());
- state.x.Assign(buttons[X - BUTTON_HID_BEGIN]->GetStatus());
- state.y.Assign(buttons[Y - BUTTON_HID_BEGIN]->GetStatus());
- state.lstick.Assign(buttons[LStick - BUTTON_HID_BEGIN]->GetStatus());
- state.rstick.Assign(buttons[RStick - BUTTON_HID_BEGIN]->GetStatus());
- state.l.Assign(buttons[L - BUTTON_HID_BEGIN]->GetStatus());
- state.r.Assign(buttons[R - BUTTON_HID_BEGIN]->GetStatus());
- state.zl.Assign(buttons[ZL - BUTTON_HID_BEGIN]->GetStatus());
- state.zr.Assign(buttons[ZR - BUTTON_HID_BEGIN]->GetStatus());
- state.plus.Assign(buttons[Plus - BUTTON_HID_BEGIN]->GetStatus());
- state.minus.Assign(buttons[Minus - BUTTON_HID_BEGIN]->GetStatus());
-
- state.dleft.Assign(buttons[DLeft - BUTTON_HID_BEGIN]->GetStatus());
- state.dup.Assign(buttons[DUp - BUTTON_HID_BEGIN]->GetStatus());
- state.dright.Assign(buttons[DRight - BUTTON_HID_BEGIN]->GetStatus());
- state.ddown.Assign(buttons[DDown - BUTTON_HID_BEGIN]->GetStatus());
-
- state.lstick_left.Assign(buttons[LStick_Left - BUTTON_HID_BEGIN]->GetStatus());
- state.lstick_up.Assign(buttons[LStick_Up - BUTTON_HID_BEGIN]->GetStatus());
- state.lstick_right.Assign(buttons[LStick_Right - BUTTON_HID_BEGIN]->GetStatus());
- state.lstick_down.Assign(buttons[LStick_Down - BUTTON_HID_BEGIN]->GetStatus());
-
- state.rstick_left.Assign(buttons[RStick_Left - BUTTON_HID_BEGIN]->GetStatus());
- state.rstick_up.Assign(buttons[RStick_Up - BUTTON_HID_BEGIN]->GetStatus());
- state.rstick_right.Assign(buttons[RStick_Right - BUTTON_HID_BEGIN]->GetStatus());
- state.rstick_down.Assign(buttons[RStick_Down - BUTTON_HID_BEGIN]->GetStatus());
-
- state.sl.Assign(buttons[SL - BUTTON_HID_BEGIN]->GetStatus());
- state.sr.Assign(buttons[SR - BUTTON_HID_BEGIN]->GetStatus());
-
- // TODO(shinyquagsire23): Analog stick vals
-
- // TODO(shinyquagsire23): Update pad info proper, (circular buffers, timestamps,
- // layouts)
+ for (size_t controller = 0; controller < mem.controllers.size(); controller++) {
+ for (int index = 0; index < HID_NUM_LAYOUTS; index++) {
+ ControllerLayout& layout = mem.controllers[controller].layouts[index];
+ layout.header.num_entries = HID_NUM_ENTRIES;
+ layout.header.max_entry_index = HID_NUM_ENTRIES - 1;
+
+ // HID shared memory stores the state of the past 17 samples in a circlular buffer,
+ // each with a timestamp in number of samples since boot.
+ layout.header.timestamp_ticks = CoreTiming::GetTicks();
+ layout.header.latest_entry = (layout.header.latest_entry + 1) % HID_NUM_ENTRIES;
+
+ ControllerInputEntry& entry = layout.entries[layout.header.latest_entry];
+ entry.timestamp++;
+ // TODO(shinyquagsire23): Is this always identical to timestamp?
+ entry.timestamp_2++;
+
+ // TODO(shinyquagsire23): More than just handheld input
+ if (controller != Controller_Handheld)
+ continue;
+
+ entry.connection_state = ConnectionState_Connected | ConnectionState_Wired;
+
+ // TODO(shinyquagsire23): Set up some LUTs for each layout mapping in the future?
+ // For now everything is just the default handheld layout, but split Joy-Con will
+ // rotate the face buttons and directions for certain layouts.
+ ControllerPadState& state = entry.buttons;
+ using namespace Settings::NativeButton;
+ state.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus());
+ state.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus());
+ state.x.Assign(buttons[X - BUTTON_HID_BEGIN]->GetStatus());
+ state.y.Assign(buttons[Y - BUTTON_HID_BEGIN]->GetStatus());
+ state.lstick.Assign(buttons[LStick - BUTTON_HID_BEGIN]->GetStatus());
+ state.rstick.Assign(buttons[RStick - BUTTON_HID_BEGIN]->GetStatus());
+ state.l.Assign(buttons[L - BUTTON_HID_BEGIN]->GetStatus());
+ state.r.Assign(buttons[R - BUTTON_HID_BEGIN]->GetStatus());
+ state.zl.Assign(buttons[ZL - BUTTON_HID_BEGIN]->GetStatus());
+ state.zr.Assign(buttons[ZR - BUTTON_HID_BEGIN]->GetStatus());
+ state.plus.Assign(buttons[Plus - BUTTON_HID_BEGIN]->GetStatus());
+ state.minus.Assign(buttons[Minus - BUTTON_HID_BEGIN]->GetStatus());
+
+ state.dleft.Assign(buttons[DLeft - BUTTON_HID_BEGIN]->GetStatus());
+ state.dup.Assign(buttons[DUp - BUTTON_HID_BEGIN]->GetStatus());
+ state.dright.Assign(buttons[DRight - BUTTON_HID_BEGIN]->GetStatus());
+ state.ddown.Assign(buttons[DDown - BUTTON_HID_BEGIN]->GetStatus());
+
+ state.lstick_left.Assign(buttons[LStick_Left - BUTTON_HID_BEGIN]->GetStatus());
+ state.lstick_up.Assign(buttons[LStick_Up - BUTTON_HID_BEGIN]->GetStatus());
+ state.lstick_right.Assign(buttons[LStick_Right - BUTTON_HID_BEGIN]->GetStatus());
+ state.lstick_down.Assign(buttons[LStick_Down - BUTTON_HID_BEGIN]->GetStatus());
+
+ state.rstick_left.Assign(buttons[RStick_Left - BUTTON_HID_BEGIN]->GetStatus());
+ state.rstick_up.Assign(buttons[RStick_Up - BUTTON_HID_BEGIN]->GetStatus());
+ state.rstick_right.Assign(buttons[RStick_Right - BUTTON_HID_BEGIN]->GetStatus());
+ state.rstick_down.Assign(buttons[RStick_Down - BUTTON_HID_BEGIN]->GetStatus());
+
+ state.sl.Assign(buttons[SL - BUTTON_HID_BEGIN]->GetStatus());
+ state.sr.Assign(buttons[SR - BUTTON_HID_BEGIN]->GetStatus());
+
+ const auto [stick_l_x_f, stick_l_y_f] = sticks[Joystick_Left]->GetStatus();
+ const auto [stick_r_x_f, stick_r_y_f] = sticks[Joystick_Right]->GetStatus();
+ entry.joystick_left_x = static_cast<s32>(stick_l_x_f * HID_JOYSTICK_MAX);
+ entry.joystick_left_y = static_cast<s32>(stick_l_y_f * HID_JOYSTICK_MAX);
+ entry.joystick_right_x = static_cast<s32>(stick_r_x_f * HID_JOYSTICK_MAX);
+ entry.joystick_right_y = static_cast<s32>(stick_r_y_f * HID_JOYSTICK_MAX);
+ }
+ }
+
+ // TODO(bunnei): Properly implement the touch screen, the below will just write empty data
+
+ TouchScreen& touchscreen = mem.touchscreen;
+ const u64 last_entry = touchscreen.header.latest_entry;
+ const u64 curr_entry = (last_entry + 1) % touchscreen.entries.size();
+ const u64 timestamp = CoreTiming::GetTicks();
+ const u64 sample_counter = touchscreen.entries[last_entry].header.timestamp + 1;
+ touchscreen.header.timestamp_ticks = timestamp;
+ touchscreen.header.num_entries = touchscreen.entries.size();
+ touchscreen.header.latest_entry = curr_entry;
+ touchscreen.header.max_entry_index = touchscreen.entries.size();
+ touchscreen.header.timestamp = timestamp;
+ touchscreen.entries[curr_entry].header.timestamp = sample_counter;
+ touchscreen.entries[curr_entry].header.num_touches = 0;
+
+ // TODO(shinyquagsire23): Properly implement mouse
+ Mouse& mouse = mem.mouse;
+ const u64 last_mouse_entry = mouse.header.latest_entry;
+ const u64 curr_mouse_entry = (mouse.header.latest_entry + 1) % mouse.entries.size();
+ const u64 mouse_sample_counter = mouse.entries[last_mouse_entry].timestamp + 1;
+ mouse.header.timestamp_ticks = timestamp;
+ mouse.header.num_entries = mouse.entries.size();
+ mouse.header.max_entry_index = mouse.entries.size();
+ mouse.header.latest_entry = curr_mouse_entry;
+
+ mouse.entries[curr_mouse_entry].timestamp = mouse_sample_counter;
+ mouse.entries[curr_mouse_entry].timestamp_2 = mouse_sample_counter;
+
+ // TODO(shinyquagsire23): Properly implement keyboard
+ Keyboard& keyboard = mem.keyboard;
+ const u64 last_keyboard_entry = keyboard.header.latest_entry;
+ const u64 curr_keyboard_entry =
+ (keyboard.header.latest_entry + 1) % keyboard.entries.size();
+ const u64 keyboard_sample_counter = keyboard.entries[last_keyboard_entry].timestamp + 1;
+ keyboard.header.timestamp_ticks = timestamp;
+ keyboard.header.num_entries = keyboard.entries.size();
+ keyboard.header.latest_entry = last_keyboard_entry;
+ keyboard.header.max_entry_index = keyboard.entries.size();
+
+ keyboard.entries[curr_keyboard_entry].timestamp = keyboard_sample_counter;
+ keyboard.entries[curr_keyboard_entry].timestamp_2 = keyboard_sample_counter;
+
+ // TODO(shinyquagsire23): Figure out what any of these are
+ for (size_t i = 0; i < mem.unk_input_1.size(); i++) {
+ UnkInput1& input = mem.unk_input_1[i];
+ const u64 last_input_entry = input.header.latest_entry;
+ const u64 curr_input_entry = (input.header.latest_entry + 1) % input.entries.size();
+ const u64 input_sample_counter = input.entries[last_input_entry].timestamp + 1;
+
+ input.header.timestamp_ticks = timestamp;
+ input.header.num_entries = input.entries.size();
+ input.header.latest_entry = last_input_entry;
+ input.header.max_entry_index = input.entries.size();
+
+ input.entries[curr_input_entry].timestamp = input_sample_counter;
+ input.entries[curr_input_entry].timestamp_2 = input_sample_counter;
}
- // TODO(shinyquagsire23): Update touch info
+ for (size_t i = 0; i < mem.unk_input_2.size(); i++) {
+ UnkInput2& input = mem.unk_input_2[i];
+
+ input.header.timestamp_ticks = timestamp;
+ input.header.num_entries = 17;
+ input.header.latest_entry = 0;
+ input.header.max_entry_index = 0;
+ }
+
+ UnkInput3& input = mem.unk_input_3;
+ const u64 last_input_entry = input.header.latest_entry;
+ const u64 curr_input_entry = (input.header.latest_entry + 1) % input.entries.size();
+ const u64 input_sample_counter = input.entries[last_input_entry].timestamp + 1;
+
+ input.header.timestamp_ticks = timestamp;
+ input.header.num_entries = input.entries.size();
+ input.header.latest_entry = last_input_entry;
+ input.header.max_entry_index = input.entries.size();
+
+ input.entries[curr_input_entry].timestamp = input_sample_counter;
+ input.entries[curr_input_entry].timestamp_2 = input_sample_counter;
// TODO(shinyquagsire23): Signal events
+ std::memcpy(shared_mem->GetPointer(), &mem, sizeof(SharedMemory));
+
// Reschedule recurrent event
CoreTiming::ScheduleEvent(pad_update_ticks - cycles_late, pad_update_event);
}
@@ -150,6 +247,7 @@ private:
std::atomic<bool> is_device_reload_pending{true};
std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID>
buttons;
+ std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID> sticks;
};
class IActiveVibrationDeviceList final : public ServiceFramework<IActiveVibrationDeviceList> {
@@ -165,7 +263,7 @@ private:
void ActivateVibrationDevice(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ NGLOG_WARNING(Service_HID, "(STUBBED) called");
}
};
@@ -178,24 +276,105 @@ public:
{11, &Hid::ActivateTouchScreen, "ActivateTouchScreen"},
{21, &Hid::ActivateMouse, "ActivateMouse"},
{31, &Hid::ActivateKeyboard, "ActivateKeyboard"},
+ {40, nullptr, "AcquireXpadIdEventHandle"},
+ {41, nullptr, "ReleaseXpadIdEventHandle"},
+ {51, nullptr, "ActivateXpad"},
+ {55, nullptr, "GetXpadIds"},
+ {56, nullptr, "ActivateJoyXpad"},
+ {58, nullptr, "GetJoyXpadLifoHandle"},
+ {59, nullptr, "GetJoyXpadIds"},
+ {60, nullptr, "ActivateSixAxisSensor"},
+ {61, nullptr, "DeactivateSixAxisSensor"},
+ {62, nullptr, "GetSixAxisSensorLifoHandle"},
+ {63, nullptr, "ActivateJoySixAxisSensor"},
+ {64, nullptr, "DeactivateJoySixAxisSensor"},
+ {65, nullptr, "GetJoySixAxisSensorLifoHandle"},
{66, &Hid::StartSixAxisSensor, "StartSixAxisSensor"},
+ {67, nullptr, "StopSixAxisSensor"},
+ {68, nullptr, "IsSixAxisSensorFusionEnabled"},
+ {69, nullptr, "EnableSixAxisSensorFusion"},
+ {70, nullptr, "SetSixAxisSensorFusionParameters"},
+ {71, nullptr, "GetSixAxisSensorFusionParameters"},
+ {72, nullptr, "ResetSixAxisSensorFusionParameters"},
+ {73, nullptr, "SetAccelerometerParameters"},
+ {74, nullptr, "GetAccelerometerParameters"},
+ {75, nullptr, "ResetAccelerometerParameters"},
+ {76, nullptr, "SetAccelerometerPlayMode"},
+ {77, nullptr, "GetAccelerometerPlayMode"},
+ {78, nullptr, "ResetAccelerometerPlayMode"},
{79, &Hid::SetGyroscopeZeroDriftMode, "SetGyroscopeZeroDriftMode"},
+ {80, nullptr, "GetGyroscopeZeroDriftMode"},
+ {81, nullptr, "ResetGyroscopeZeroDriftMode"},
+ {82, nullptr, "IsSixAxisSensorAtRest"},
+ {91, nullptr, "ActivateGesture"},
{100, &Hid::SetSupportedNpadStyleSet, "SetSupportedNpadStyleSet"},
+ {101, &Hid::GetSupportedNpadStyleSet, "GetSupportedNpadStyleSet"},
{102, &Hid::SetSupportedNpadIdType, "SetSupportedNpadIdType"},
{103, &Hid::ActivateNpad, "ActivateNpad"},
+ {104, nullptr, "DeactivateNpad"},
{106, &Hid::AcquireNpadStyleSetUpdateEventHandle,
"AcquireNpadStyleSetUpdateEventHandle"},
+ {107, nullptr, "DisconnectNpad"},
+ {108, &Hid::GetPlayerLedPattern, "GetPlayerLedPattern"},
{120, &Hid::SetNpadJoyHoldType, "SetNpadJoyHoldType"},
{121, &Hid::GetNpadJoyHoldType, "GetNpadJoyHoldType"},
{122, &Hid::SetNpadJoyAssignmentModeSingleByDefault,
"SetNpadJoyAssignmentModeSingleByDefault"},
- {124, nullptr, "SetNpadJoyAssignmentModeDual"},
+ {123, nullptr, "SetNpadJoyAssignmentModeSingleByDefault"},
+ {124, &Hid::SetNpadJoyAssignmentModeDual, "SetNpadJoyAssignmentModeDual"},
+ {125, nullptr, "MergeSingleJoyAsDualJoy"},
+ {126, nullptr, "StartLrAssignmentMode"},
+ {127, nullptr, "StopLrAssignmentMode"},
{128, &Hid::SetNpadHandheldActivationMode, "SetNpadHandheldActivationMode"},
+ {129, nullptr, "GetNpadHandheldActivationMode"},
+ {130, nullptr, "SwapNpadAssignment"},
+ {131, nullptr, "IsUnintendedHomeButtonInputProtectionEnabled"},
+ {132, nullptr, "EnableUnintendedHomeButtonInputProtection"},
+ {133, nullptr, "SetNpadJoyAssignmentModeSingleWithDestination"},
{200, &Hid::GetVibrationDeviceInfo, "GetVibrationDeviceInfo"},
{201, &Hid::SendVibrationValue, "SendVibrationValue"},
{202, &Hid::GetActualVibrationValue, "GetActualVibrationValue"},
{203, &Hid::CreateActiveVibrationDeviceList, "CreateActiveVibrationDeviceList"},
+ {204, nullptr, "PermitVibration"},
+ {205, nullptr, "IsVibrationPermitted"},
{206, &Hid::SendVibrationValues, "SendVibrationValues"},
+ {207, nullptr, "SendVibrationGcErmCommand"},
+ {208, nullptr, "GetActualVibrationGcErmCommand"},
+ {209, nullptr, "BeginPermitVibrationSession"},
+ {210, nullptr, "EndPermitVibrationSession"},
+ {300, nullptr, "ActivateConsoleSixAxisSensor"},
+ {301, nullptr, "StartConsoleSixAxisSensor"},
+ {302, nullptr, "StopConsoleSixAxisSensor"},
+ {303, nullptr, "ActivateSevenSixAxisSensor"},
+ {304, nullptr, "StartSevenSixAxisSensor"},
+ {305, nullptr, "StopSevenSixAxisSensor"},
+ {306, nullptr, "InitializeSevenSixAxisSensor"},
+ {307, nullptr, "FinalizeSevenSixAxisSensor"},
+ {308, nullptr, "SetSevenSixAxisSensorFusionStrength"},
+ {309, nullptr, "GetSevenSixAxisSensorFusionStrength"},
+ {400, nullptr, "IsUsbFullKeyControllerEnabled"},
+ {401, nullptr, "EnableUsbFullKeyController"},
+ {402, nullptr, "IsUsbFullKeyControllerConnected"},
+ {403, nullptr, "HasBattery"},
+ {404, nullptr, "HasLeftRightBattery"},
+ {405, nullptr, "GetNpadInterfaceType"},
+ {406, nullptr, "GetNpadLeftRightInterfaceType"},
+ {500, nullptr, "GetPalmaConnectionHandle"},
+ {501, nullptr, "InitializePalma"},
+ {502, nullptr, "AcquirePalmaOperationCompleteEvent"},
+ {503, nullptr, "GetPalmaOperationInfo"},
+ {504, nullptr, "PlayPalmaActivity"},
+ {505, nullptr, "SetPalmaFrModeType"},
+ {506, nullptr, "ReadPalmaStep"},
+ {507, nullptr, "EnablePalmaStep"},
+ {508, nullptr, "SuspendPalmaStep"},
+ {509, nullptr, "ResetPalmaStep"},
+ {510, nullptr, "ReadPalmaApplicationSection"},
+ {511, nullptr, "WritePalmaApplicationSection"},
+ {512, nullptr, "ReadPalmaUniqueCode"},
+ {513, nullptr, "SetPalmaUniqueCodeInvalid"},
+ {1000, nullptr, "SetNpadCommunicationMode"},
+ {1001, nullptr, "GetNpadCommunicationMode"},
};
RegisterHandlers(functions);
@@ -216,125 +395,144 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IAppletResource>(applet_resource);
- LOG_DEBUG(Service_HID, "called");
+ NGLOG_DEBUG(Service_HID, "called");
}
void ActivateDebugPad(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ NGLOG_WARNING(Service_HID, "(STUBBED) called");
}
void ActivateTouchScreen(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ NGLOG_WARNING(Service_HID, "(STUBBED) called");
}
void ActivateMouse(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ NGLOG_WARNING(Service_HID, "(STUBBED) called");
}
void ActivateKeyboard(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ NGLOG_WARNING(Service_HID, "(STUBBED) called");
}
void StartSixAxisSensor(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ NGLOG_WARNING(Service_HID, "(STUBBED) called");
}
void SetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ NGLOG_WARNING(Service_HID, "(STUBBED) called");
}
void SetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ NGLOG_WARNING(Service_HID, "(STUBBED) called");
+ }
+
+ void GetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(0);
+ NGLOG_WARNING(Service_HID, "(STUBBED) called");
}
void SetSupportedNpadIdType(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ NGLOG_WARNING(Service_HID, "(STUBBED) called");
}
void ActivateNpad(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ NGLOG_WARNING(Service_HID, "(STUBBED) called");
}
void AcquireNpadStyleSetUpdateEventHandle(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
rb.PushCopyObjects(event);
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ NGLOG_WARNING(Service_HID, "(STUBBED) called");
+ }
+
+ void GetPlayerLedPattern(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ NGLOG_WARNING(Service_HID, "(STUBBED) called");
}
void SetNpadJoyHoldType(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ NGLOG_WARNING(Service_HID, "(STUBBED) called");
}
void GetNpadJoyHoldType(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push(joy_hold_type);
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ NGLOG_WARNING(Service_HID, "(STUBBED) called");
}
void SetNpadJoyAssignmentModeSingleByDefault(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ NGLOG_WARNING(Service_HID, "(STUBBED) called");
}
void SendVibrationValue(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ NGLOG_WARNING(Service_HID, "(STUBBED) called");
}
void GetActualVibrationValue(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ NGLOG_WARNING(Service_HID, "(STUBBED) called");
+ }
+
+ void SetNpadJoyAssignmentModeDual(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ NGLOG_WARNING(Service_HID, "(STUBBED) called");
}
void SetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ NGLOG_WARNING(Service_HID, "(STUBBED) called");
}
void GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
rb.Push<u64>(0);
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ NGLOG_WARNING(Service_HID, "(STUBBED) called");
}
void CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IActiveVibrationDeviceList>();
- LOG_DEBUG(Service_HID, "called");
+ NGLOG_DEBUG(Service_HID, "called");
}
void SendVibrationValues(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ NGLOG_WARNING(Service_HID, "(STUBBED) called");
}
};
@@ -344,5 +542,4 @@ void InstallInterfaces(SM::ServiceManager& service_manager) {
std::make_shared<Hid>()->InstallAsService(service_manager);
}
-} // namespace HID
-} // namespace Service
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h
index 3de9adb4b..b499308d6 100644
--- a/src/core/hle/service/hid/hid.h
+++ b/src/core/hle/service/hid/hid.h
@@ -7,8 +7,7 @@
#include "core/hle/service/service.h"
#include "core/settings.h"
-namespace Service {
-namespace HID {
+namespace Service::HID {
// Begin enums and output structs
@@ -49,6 +48,11 @@ enum ControllerConnectionState {
ConnectionState_Wired = 1 << 1,
};
+enum ControllerJoystick {
+ Joystick_Left = 0,
+ Joystick_Right = 1,
+};
+
enum ControllerID {
Controller_Player1 = 0,
Controller_Player2 = 1,
@@ -64,6 +68,34 @@ enum ControllerID {
// End enums and output structs
+// Begin UnkInput3
+
+struct UnkInput3Header {
+ u64 timestamp_ticks;
+ u64 num_entries;
+ u64 latest_entry;
+ u64 max_entry_index;
+};
+static_assert(sizeof(UnkInput3Header) == 0x20, "HID UnkInput3 header structure has incorrect size");
+
+struct UnkInput3Entry {
+ u64 timestamp;
+ u64 timestamp_2;
+ u64 unk_8;
+ u64 unk_10;
+ u64 unk_18;
+};
+static_assert(sizeof(UnkInput3Entry) == 0x28, "HID UnkInput3 entry structure has incorrect size");
+
+struct UnkInput3 {
+ UnkInput3Header header;
+ std::array<UnkInput3Entry, 17> entries;
+ std::array<u8, 0x138> padding;
+};
+static_assert(sizeof(UnkInput3) == 0x400, "HID UnkInput3 structure has incorrect size");
+
+// End UnkInput3
+
// Begin TouchScreen
struct TouchScreenHeader {
@@ -205,6 +237,52 @@ static_assert(sizeof(Keyboard) == 0x400, "HID keyboard structure has incorrect s
// End Keyboard
+// Begin UnkInput1
+
+struct UnkInput1Header {
+ u64 timestamp_ticks;
+ u64 num_entries;
+ u64 latest_entry;
+ u64 max_entry_index;
+};
+static_assert(sizeof(UnkInput1Header) == 0x20, "HID UnkInput1 header structure has incorrect size");
+
+struct UnkInput1Entry {
+ u64 timestamp;
+ u64 timestamp_2;
+ u64 unk_8;
+ u64 unk_10;
+ u64 unk_18;
+};
+static_assert(sizeof(UnkInput1Entry) == 0x28, "HID UnkInput1 entry structure has incorrect size");
+
+struct UnkInput1 {
+ UnkInput1Header header;
+ std::array<UnkInput1Entry, 17> entries;
+ std::array<u8, 0x138> padding;
+};
+static_assert(sizeof(UnkInput1) == 0x400, "HID UnkInput1 structure has incorrect size");
+
+// End UnkInput1
+
+// Begin UnkInput2
+
+struct UnkInput2Header {
+ u64 timestamp_ticks;
+ u64 num_entries;
+ u64 latest_entry;
+ u64 max_entry_index;
+};
+static_assert(sizeof(UnkInput2Header) == 0x20, "HID UnkInput2 header structure has incorrect size");
+
+struct UnkInput2 {
+ UnkInput2Header header;
+ std::array<u8, 0x1E0> padding;
+};
+static_assert(sizeof(UnkInput2) == 0x200, "HID UnkInput2 structure has incorrect size");
+
+// End UnkInput2
+
// Begin Controller
struct ControllerMAC {
@@ -284,10 +362,10 @@ struct ControllerInputEntry {
u64 timestamp;
u64 timestamp_2;
ControllerPadState buttons;
- u32 joystick_left_x;
- u32 joystick_left_y;
- u32 joystick_right_x;
- u32 joystick_right_y;
+ s32 joystick_left_x;
+ s32 joystick_left_y;
+ s32 joystick_right_x;
+ s32 joystick_right_y;
u64 connection_state;
};
static_assert(sizeof(ControllerInputEntry) == 0x30,
@@ -313,17 +391,12 @@ static_assert(sizeof(Controller) == 0x5000, "HID controller structure has incorr
// End Controller
struct SharedMemory {
- std::array<u8, 0x400> header;
+ UnkInput3 unk_input_3;
TouchScreen touchscreen;
Mouse mouse;
Keyboard keyboard;
- std::array<u8, 0x400> unk_section_1;
- std::array<u8, 0x400> unk_section_2;
- std::array<u8, 0x400> unk_section_3;
- std::array<u8, 0x400> unk_section_4;
- std::array<u8, 0x200> unk_section_5;
- std::array<u8, 0x200> unk_section_6;
- std::array<u8, 0x200> unk_section_7;
+ std::array<UnkInput1, 4> unk_input_1;
+ std::array<UnkInput2, 3> unk_input_2;
std::array<u8, 0x800> unk_section_8;
std::array<u8, 0x4000> controller_serials;
std::array<Controller, 10> controllers;
@@ -337,5 +410,4 @@ void ReloadInputDevices();
/// Registers all HID services with the specified service manager.
void InstallInterfaces(SM::ServiceManager& service_manager);
-} // namespace HID
-} // namespace Service
+} // namespace Service::HID
diff --git a/src/core/hle/service/lm/lm.cpp b/src/core/hle/service/lm/lm.cpp
index b8e53d2c7..46194643e 100644
--- a/src/core/hle/service/lm/lm.cpp
+++ b/src/core/hle/service/lm/lm.cpp
@@ -9,8 +9,7 @@
#include "core/hle/kernel/client_session.h"
#include "core/hle/service/lm/lm.h"
-namespace Service {
-namespace LM {
+namespace Service::LM {
class Logger final : public ServiceFramework<Logger> {
public:
@@ -142,19 +141,19 @@ private:
if (header.IsTailLog()) {
switch (header.severity) {
case MessageHeader::Severity::Trace:
- LOG_TRACE(Debug_Emulated, "%s", log_stream.str().c_str());
+ NGLOG_TRACE(Debug_Emulated, "{}", log_stream.str());
break;
case MessageHeader::Severity::Info:
- LOG_INFO(Debug_Emulated, "%s", log_stream.str().c_str());
+ NGLOG_INFO(Debug_Emulated, "{}", log_stream.str());
break;
case MessageHeader::Severity::Warning:
- LOG_WARNING(Debug_Emulated, "%s", log_stream.str().c_str());
+ NGLOG_WARNING(Debug_Emulated, "{}", log_stream.str());
break;
case MessageHeader::Severity::Error:
- LOG_ERROR(Debug_Emulated, "%s", log_stream.str().c_str());
+ NGLOG_ERROR(Debug_Emulated, "{}", log_stream.str());
break;
case MessageHeader::Severity::Critical:
- LOG_CRITICAL(Debug_Emulated, "%s", log_stream.str().c_str());
+ NGLOG_CRITICAL(Debug_Emulated, "{}", log_stream.str());
break;
}
}
@@ -179,7 +178,7 @@ void LM::Initialize(Kernel::HLERequestContext& ctx) {
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<Logger>();
- LOG_DEBUG(Service_LM, "called");
+ NGLOG_DEBUG(Service_LM, "called");
}
LM::LM() : ServiceFramework("lm") {
@@ -189,5 +188,4 @@ LM::LM() : ServiceFramework("lm") {
RegisterHandlers(functions);
}
-} // namespace LM
-} // namespace Service
+} // namespace Service::LM
diff --git a/src/core/hle/service/lm/lm.h b/src/core/hle/service/lm/lm.h
index 371135057..63d6506fe 100644
--- a/src/core/hle/service/lm/lm.h
+++ b/src/core/hle/service/lm/lm.h
@@ -8,8 +8,7 @@
#include "core/hle/kernel/kernel.h"
#include "core/hle/service/service.h"
-namespace Service {
-namespace LM {
+namespace Service::LM {
class LM final : public ServiceFramework<LM> {
public:
@@ -23,5 +22,4 @@ private:
/// Registers all LM services with the specified service manager.
void InstallInterfaces(SM::ServiceManager& service_manager);
-} // namespace LM
-} // namespace Service
+} // namespace Service::LM
diff --git a/src/core/hle/service/mm/mm_u.cpp b/src/core/hle/service/mm/mm_u.cpp
new file mode 100644
index 000000000..b3a85b818
--- /dev/null
+++ b/src/core/hle/service/mm/mm_u.cpp
@@ -0,0 +1,50 @@
+// Copyright 2018 yuzu emulator team
+// 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/client_session.h"
+#include "core/hle/service/mm/mm_u.h"
+
+namespace Service::MM {
+
+void InstallInterfaces(SM::ServiceManager& service_manager) {
+ std::make_shared<MM_U>()->InstallAsService(service_manager);
+}
+
+void MM_U::Initialize(Kernel::HLERequestContext& ctx) {
+ NGLOG_WARNING(Service_MM, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
+void MM_U::SetAndWait(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ min = rp.Pop<u32>();
+ max = rp.Pop<u32>();
+ current = min;
+
+ NGLOG_WARNING(Service_MM, "(STUBBED) called, min=0x{:X}, max=0x{:X}", min, max);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
+void MM_U::Get(Kernel::HLERequestContext& ctx) {
+ NGLOG_WARNING(Service_MM, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(current);
+}
+
+MM_U::MM_U() : ServiceFramework("mm:u") {
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "InitializeOld"}, {1, nullptr, "FinalizeOld"},
+ {2, nullptr, "SetAndWaitOld"}, {3, nullptr, "GetOld"},
+ {4, &MM_U::Initialize, "Initialize"}, {5, nullptr, "Finalize"},
+ {6, &MM_U::SetAndWait, "SetAndWait"}, {7, &MM_U::Get, "Get"},
+ };
+ RegisterHandlers(functions);
+}
+
+} // namespace Service::MM
diff --git a/src/core/hle/service/mm/mm_u.h b/src/core/hle/service/mm/mm_u.h
new file mode 100644
index 000000000..79eeedf9c
--- /dev/null
+++ b/src/core/hle/service/mm/mm_u.h
@@ -0,0 +1,29 @@
+// 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/service.h"
+
+namespace Service::MM {
+
+class MM_U final : public ServiceFramework<MM_U> {
+public:
+ MM_U();
+ ~MM_U() = default;
+
+private:
+ void Initialize(Kernel::HLERequestContext& ctx);
+ void SetAndWait(Kernel::HLERequestContext& ctx);
+ void Get(Kernel::HLERequestContext& ctx);
+
+ u32 min{0};
+ u32 max{0};
+ u32 current{0};
+};
+
+/// Registers all MM services with the specified service manager.
+void InstallInterfaces(SM::ServiceManager& service_manager);
+
+} // namespace Service::MM
diff --git a/src/core/hle/service/nfp/nfp.cpp b/src/core/hle/service/nfp/nfp.cpp
new file mode 100644
index 000000000..2a9f84037
--- /dev/null
+++ b/src/core/hle/service/nfp/nfp.cpp
@@ -0,0 +1,162 @@
+// Copyright 2018 yuzu emulator team
+// 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/event.h"
+#include "core/hle/service/hid/hid.h"
+#include "core/hle/service/nfp/nfp.h"
+#include "core/hle/service/nfp/nfp_user.h"
+
+namespace Service::NFP {
+
+Module::Interface::Interface(std::shared_ptr<Module> module, const char* name)
+ : ServiceFramework(name), module(std::move(module)) {}
+
+class IUser final : public ServiceFramework<IUser> {
+public:
+ IUser() : ServiceFramework("IUser") {
+ static const FunctionInfo functions[] = {
+ {0, &IUser::Initialize, "Initialize"},
+ {1, nullptr, "Finalize"},
+ {2, &IUser::ListDevices, "ListDevices"},
+ {3, nullptr, "StartDetection"},
+ {4, nullptr, "StopDetection"},
+ {5, nullptr, "Mount"},
+ {6, nullptr, "Unmount"},
+ {7, nullptr, "OpenApplicationArea"},
+ {8, nullptr, "GetApplicationArea"},
+ {9, nullptr, "SetApplicationArea"},
+ {10, nullptr, "Flush"},
+ {11, nullptr, "Restore"},
+ {12, nullptr, "CreateApplicationArea"},
+ {13, nullptr, "GetTagInfo"},
+ {14, nullptr, "GetRegisterInfo"},
+ {15, nullptr, "GetCommonInfo"},
+ {16, nullptr, "GetModelInfo"},
+ {17, &IUser::AttachActivateEvent, "AttachActivateEvent"},
+ {18, &IUser::AttachDeactivateEvent, "AttachDeactivateEvent"},
+ {19, &IUser::GetState, "GetState"},
+ {20, &IUser::GetDeviceState, "GetDeviceState"},
+ {21, &IUser::GetNpadId, "GetNpadId"},
+ {22, nullptr, "GetApplicationArea2"},
+ {23, &IUser::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"},
+ {24, nullptr, "RecreateApplicationArea"},
+ };
+ RegisterHandlers(functions);
+
+ activate_event = Kernel::Event::Create(Kernel::ResetType::OneShot, "IUser:ActivateEvent");
+ deactivate_event =
+ Kernel::Event::Create(Kernel::ResetType::OneShot, "IUser:DeactivateEvent");
+ availability_change_event =
+ Kernel::Event::Create(Kernel::ResetType::OneShot, "IUser:AvailabilityChangeEvent");
+ }
+
+private:
+ enum class State : u32 {
+ NonInitialized = 0,
+ Initialized = 1,
+ };
+
+ enum class DeviceState : u32 {
+ Initialized = 0,
+ };
+
+ void Initialize(Kernel::HLERequestContext& ctx) {
+ NGLOG_WARNING(Service_NFP, "(STUBBED) called");
+
+ state = State::Initialized;
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void ListDevices(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const u32 array_size = rp.Pop<u32>();
+
+ ctx.WriteBuffer(&device_handle, sizeof(device_handle));
+
+ NGLOG_WARNING(Service_NFP, "(STUBBED) called, array_size={}", array_size);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(0);
+ }
+
+ void AttachActivateEvent(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const u64 dev_handle = rp.Pop<u64>();
+ NGLOG_WARNING(Service_NFP, "(STUBBED) called, dev_handle=0x{:X}", dev_handle);
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushCopyObjects(activate_event);
+ }
+
+ void AttachDeactivateEvent(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const u64 dev_handle = rp.Pop<u64>();
+ NGLOG_WARNING(Service_NFP, "(STUBBED) called, dev_handle=0x{:X}", dev_handle);
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushCopyObjects(deactivate_event);
+ }
+
+ void GetState(Kernel::HLERequestContext& ctx) {
+ NGLOG_WARNING(Service_NFP, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(static_cast<u32>(state));
+ }
+
+ void GetDeviceState(Kernel::HLERequestContext& ctx) {
+ NGLOG_WARNING(Service_NFP, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(static_cast<u32>(device_state));
+ }
+
+ void GetNpadId(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const u64 dev_handle = rp.Pop<u64>();
+ NGLOG_WARNING(Service_NFP, "(STUBBED) called, dev_handle=0x{:X}", dev_handle);
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(npad_id);
+ }
+
+ void AttachAvailabilityChangeEvent(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const u64 dev_handle = rp.Pop<u64>();
+ NGLOG_WARNING(Service_NFP, "(STUBBED) called, dev_handle=0x{:X}", dev_handle);
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushCopyObjects(availability_change_event);
+ }
+
+ const u64 device_handle{0xDEAD};
+ const HID::ControllerID npad_id{HID::Controller_Player1};
+ State state{State::NonInitialized};
+ DeviceState device_state{DeviceState::Initialized};
+ Kernel::SharedPtr<Kernel::Event> activate_event;
+ Kernel::SharedPtr<Kernel::Event> deactivate_event;
+ Kernel::SharedPtr<Kernel::Event> availability_change_event;
+};
+
+void Module::Interface::CreateUserInterface(Kernel::HLERequestContext& ctx) {
+ NGLOG_DEBUG(Service_NFP, "called");
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IUser>();
+}
+
+void InstallInterfaces(SM::ServiceManager& service_manager) {
+ auto module = std::make_shared<Module>();
+ std::make_shared<NFP_User>(module)->InstallAsService(service_manager);
+}
+
+} // namespace Service::NFP
diff --git a/src/core/hle/service/nfp/nfp.h b/src/core/hle/service/nfp/nfp.h
new file mode 100644
index 000000000..262a666cb
--- /dev/null
+++ b/src/core/hle/service/nfp/nfp.h
@@ -0,0 +1,26 @@
+// 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/service.h"
+
+namespace Service::NFP {
+
+class Module final {
+public:
+ class Interface : public ServiceFramework<Interface> {
+ public:
+ Interface(std::shared_ptr<Module> module, const char* name);
+
+ void CreateUserInterface(Kernel::HLERequestContext& ctx);
+
+ protected:
+ std::shared_ptr<Module> module;
+ };
+};
+
+void InstallInterfaces(SM::ServiceManager& service_manager);
+
+} // namespace Service::NFP
diff --git a/src/core/hle/service/nfp/nfp_user.cpp b/src/core/hle/service/nfp/nfp_user.cpp
new file mode 100644
index 000000000..b608fe693
--- /dev/null
+++ b/src/core/hle/service/nfp/nfp_user.cpp
@@ -0,0 +1,17 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/nfp/nfp_user.h"
+
+namespace Service::NFP {
+
+NFP_User::NFP_User(std::shared_ptr<Module> module)
+ : Module::Interface(std::move(module), "nfp:user") {
+ static const FunctionInfo functions[] = {
+ {0, &NFP_User::CreateUserInterface, "CreateUserInterface"},
+ };
+ RegisterHandlers(functions);
+}
+
+} // namespace Service::NFP
diff --git a/src/core/hle/service/nfp/nfp_user.h b/src/core/hle/service/nfp/nfp_user.h
new file mode 100644
index 000000000..700043114
--- /dev/null
+++ b/src/core/hle/service/nfp/nfp_user.h
@@ -0,0 +1,16 @@
+// 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/nfp/nfp.h"
+
+namespace Service::NFP {
+
+class NFP_User final : public Module::Interface {
+public:
+ explicit NFP_User(std::shared_ptr<Module> module);
+};
+
+} // namespace Service::NFP
diff --git a/src/core/hle/service/nifm/nifm.cpp b/src/core/hle/service/nifm/nifm.cpp
index e6f05eae5..62489c7fe 100644
--- a/src/core/hle/service/nifm/nifm.cpp
+++ b/src/core/hle/service/nifm/nifm.cpp
@@ -9,8 +9,7 @@
#include "core/hle/service/nifm/nifm_s.h"
#include "core/hle/service/nifm/nifm_u.h"
-namespace Service {
-namespace NIFM {
+namespace Service::NIFM {
class IScanRequest final : public ServiceFramework<IScanRequest> {
public:
@@ -32,14 +31,14 @@ public:
{0, &IRequest::GetRequestState, "GetRequestState"},
{1, &IRequest::GetResult, "GetResult"},
{2, &IRequest::GetSystemEventReadableHandles, "GetSystemEventReadableHandles"},
- {3, nullptr, "Cancel"},
+ {3, &IRequest::Cancel, "Cancel"},
{4, nullptr, "Submit"},
{5, nullptr, "SetRequirement"},
{6, nullptr, "SetRequirementPreset"},
{8, nullptr, "SetPriority"},
{9, nullptr, "SetNetworkProfileId"},
{10, nullptr, "SetRejectable"},
- {11, nullptr, "SetConnectionConfirmationOption"},
+ {11, &IRequest::SetConnectionConfirmationOption, "SetConnectionConfirmationOption"},
{12, nullptr, "SetPersistent"},
{13, nullptr, "SetInstant"},
{14, nullptr, "SetSustainable"},
@@ -63,24 +62,37 @@ public:
private:
void GetRequestState(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_NIFM, "(STUBBED) called");
+ NGLOG_WARNING(Service_NIFM, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(0);
}
+
void GetResult(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_NIFM, "(STUBBED) called");
- IPC::ResponseBuilder rb{ctx, 3};
+ NGLOG_WARNING(Service_NIFM, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(0);
}
+
void GetSystemEventReadableHandles(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_NIFM, "(STUBBED) called");
+ NGLOG_WARNING(Service_NIFM, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2, 2};
rb.Push(RESULT_SUCCESS);
rb.PushCopyObjects(event1, event2);
}
+ void Cancel(Kernel::HLERequestContext& ctx) {
+ NGLOG_WARNING(Service_NIFM, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void SetConnectionConfirmationOption(Kernel::HLERequestContext& ctx) {
+ NGLOG_WARNING(Service_NIFM, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
Kernel::SharedPtr<Kernel::Event> event1, event2;
};
@@ -96,13 +108,56 @@ public:
}
};
+class IGeneralService final : public ServiceFramework<IGeneralService> {
+public:
+ IGeneralService();
+
+private:
+ void GetClientId(Kernel::HLERequestContext& ctx) {
+ NGLOG_WARNING(Service_NIFM, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u64>(0);
+ }
+ void CreateScanRequest(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IScanRequest>();
+
+ NGLOG_DEBUG(Service_NIFM, "called");
+ }
+ void CreateRequest(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IRequest>();
+
+ NGLOG_DEBUG(Service_NIFM, "called");
+ }
+ void RemoveNetworkProfile(Kernel::HLERequestContext& ctx) {
+ NGLOG_WARNING(Service_NIFM, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+ void CreateTemporaryNetworkProfile(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<INetworkProfile>();
+
+ NGLOG_DEBUG(Service_NIFM, "called");
+ }
+};
+
IGeneralService::IGeneralService() : ServiceFramework("IGeneralService") {
static const FunctionInfo functions[] = {
{1, &IGeneralService::GetClientId, "GetClientId"},
{2, &IGeneralService::CreateScanRequest, "CreateScanRequest"},
{4, &IGeneralService::CreateRequest, "CreateRequest"},
- {6, nullptr, "GetCurrentNetworkProfile"},
- {7, nullptr, "EnumerateNetworkInterfaces"},
+ {5, nullptr, "GetCurrentNetworkProfile"},
+ {6, nullptr, "EnumerateNetworkInterfaces"},
+ {7, nullptr, "EnumerateNetworkProfiles"},
{8, nullptr, "GetNetworkProfile"},
{9, nullptr, "SetNetworkProfile"},
{10, &IGeneralService::RemoveNetworkProfile, "RemoveNetworkProfile"},
@@ -137,51 +192,28 @@ IGeneralService::IGeneralService() : ServiceFramework("IGeneralService") {
RegisterHandlers(functions);
}
-void IGeneralService::GetClientId(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_NIFM, "(STUBBED) called");
- IPC::ResponseBuilder rb{ctx, 4};
- rb.Push(RESULT_SUCCESS);
- rb.Push<u64>(0);
-}
-
-void IGeneralService::CreateScanRequest(Kernel::HLERequestContext& ctx) {
+void Module::Interface::CreateGeneralServiceOld(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
-
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IScanRequest>();
-
- LOG_DEBUG(Service_NIFM, "called");
+ rb.PushIpcInterface<IGeneralService>();
+ NGLOG_DEBUG(Service_NIFM, "called");
}
-void IGeneralService::CreateRequest(Kernel::HLERequestContext& ctx) {
+void Module::Interface::CreateGeneralService(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
-
- rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IRequest>();
-
- LOG_DEBUG(Service_NIFM, "called");
-}
-
-void IGeneralService::RemoveNetworkProfile(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_NIFM, "(STUBBED) called");
- IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IGeneralService>();
+ NGLOG_DEBUG(Service_NIFM, "called");
}
-void IGeneralService::CreateTemporaryNetworkProfile(Kernel::HLERequestContext& ctx) {
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
-
- rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<INetworkProfile>();
-
- LOG_DEBUG(Service_NIFM, "called");
-}
+Module::Interface::Interface(std::shared_ptr<Module> module, const char* name)
+ : ServiceFramework(name), module(std::move(module)) {}
void InstallInterfaces(SM::ServiceManager& service_manager) {
- std::make_shared<NIFM_A>()->InstallAsService(service_manager);
- std::make_shared<NIFM_S>()->InstallAsService(service_manager);
- std::make_shared<NIFM_U>()->InstallAsService(service_manager);
+ auto module = std::make_shared<Module>();
+ std::make_shared<NIFM_A>(module)->InstallAsService(service_manager);
+ std::make_shared<NIFM_S>(module)->InstallAsService(service_manager);
+ std::make_shared<NIFM_U>(module)->InstallAsService(service_manager);
}
-} // namespace NIFM
-} // namespace Service
+} // namespace Service::NIFM
diff --git a/src/core/hle/service/nifm/nifm.h b/src/core/hle/service/nifm/nifm.h
index 6edbfe4a4..4ad3f3bcf 100644
--- a/src/core/hle/service/nifm/nifm.h
+++ b/src/core/hle/service/nifm/nifm.h
@@ -6,22 +6,22 @@
#include "core/hle/service/service.h"
-namespace Service {
-namespace NIFM {
+namespace Service::NIFM {
-class IGeneralService final : public ServiceFramework<IGeneralService> {
+class Module final {
public:
- IGeneralService();
-
-private:
- void GetClientId(Kernel::HLERequestContext& ctx);
- void CreateScanRequest(Kernel::HLERequestContext& ctx);
- void CreateRequest(Kernel::HLERequestContext& ctx);
- void RemoveNetworkProfile(Kernel::HLERequestContext& ctx);
- void CreateTemporaryNetworkProfile(Kernel::HLERequestContext& ctx);
+ class Interface : public ServiceFramework<Interface> {
+ public:
+ Interface(std::shared_ptr<Module> module, const char* name);
+
+ void CreateGeneralServiceOld(Kernel::HLERequestContext& ctx);
+ void CreateGeneralService(Kernel::HLERequestContext& ctx);
+
+ protected:
+ std::shared_ptr<Module> module;
+ };
};
void InstallInterfaces(SM::ServiceManager& service_manager);
-} // namespace NIFM
-} // namespace Service
+} // namespace Service::NIFM
diff --git a/src/core/hle/service/nifm/nifm_a.cpp b/src/core/hle/service/nifm/nifm_a.cpp
index ee61d8ff4..b7f296a20 100644
--- a/src/core/hle/service/nifm/nifm_a.cpp
+++ b/src/core/hle/service/nifm/nifm_a.cpp
@@ -2,29 +2,11 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include "common/logging/log.h"
-#include "core/hle/ipc_helpers.h"
-#include "core/hle/service/nifm/nifm.h"
#include "core/hle/service/nifm/nifm_a.h"
-namespace Service {
-namespace NIFM {
+namespace Service::NIFM {
-void NIFM_A::CreateGeneralServiceOld(Kernel::HLERequestContext& ctx) {
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IGeneralService>();
- LOG_DEBUG(Service_NIFM, "called");
-}
-
-void NIFM_A::CreateGeneralService(Kernel::HLERequestContext& ctx) {
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IGeneralService>();
- LOG_DEBUG(Service_NIFM, "called");
-}
-
-NIFM_A::NIFM_A() : ServiceFramework("nifm:a") {
+NIFM_A::NIFM_A(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "nifm:a") {
static const FunctionInfo functions[] = {
{4, &NIFM_A::CreateGeneralServiceOld, "CreateGeneralServiceOld"},
{5, &NIFM_A::CreateGeneralService, "CreateGeneralService"},
@@ -32,5 +14,4 @@ NIFM_A::NIFM_A() : ServiceFramework("nifm:a") {
RegisterHandlers(functions);
}
-} // namespace NIFM
-} // namespace Service
+} // namespace Service::NIFM
diff --git a/src/core/hle/service/nifm/nifm_a.h b/src/core/hle/service/nifm/nifm_a.h
index 06a92a93c..c3ba33110 100644
--- a/src/core/hle/service/nifm/nifm_a.h
+++ b/src/core/hle/service/nifm/nifm_a.h
@@ -4,21 +4,13 @@
#pragma once
-#include "core/hle/kernel/hle_ipc.h"
-#include "core/hle/service/service.h"
+#include "core/hle/service/nifm/nifm.h"
-namespace Service {
-namespace NIFM {
+namespace Service::NIFM {
-class NIFM_A final : public ServiceFramework<NIFM_A> {
+class NIFM_A final : public Module::Interface {
public:
- NIFM_A();
- ~NIFM_A() = default;
-
-private:
- void CreateGeneralServiceOld(Kernel::HLERequestContext& ctx);
- void CreateGeneralService(Kernel::HLERequestContext& ctx);
+ explicit NIFM_A(std::shared_ptr<Module> module);
};
-} // namespace NIFM
-} // namespace Service
+} // namespace Service::NIFM
diff --git a/src/core/hle/service/nifm/nifm_s.cpp b/src/core/hle/service/nifm/nifm_s.cpp
index c38b2a4c7..96e3c0cee 100644
--- a/src/core/hle/service/nifm/nifm_s.cpp
+++ b/src/core/hle/service/nifm/nifm_s.cpp
@@ -2,29 +2,11 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include "common/logging/log.h"
-#include "core/hle/ipc_helpers.h"
-#include "core/hle/service/nifm/nifm.h"
#include "core/hle/service/nifm/nifm_s.h"
-namespace Service {
-namespace NIFM {
+namespace Service::NIFM {
-void NIFM_S::CreateGeneralServiceOld(Kernel::HLERequestContext& ctx) {
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IGeneralService>();
- LOG_DEBUG(Service_NIFM, "called");
-}
-
-void NIFM_S::CreateGeneralService(Kernel::HLERequestContext& ctx) {
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IGeneralService>();
- LOG_DEBUG(Service_NIFM, "called");
-}
-
-NIFM_S::NIFM_S() : ServiceFramework("nifm:s") {
+NIFM_S::NIFM_S(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "nifm:s") {
static const FunctionInfo functions[] = {
{4, &NIFM_S::CreateGeneralServiceOld, "CreateGeneralServiceOld"},
{5, &NIFM_S::CreateGeneralService, "CreateGeneralService"},
@@ -32,5 +14,4 @@ NIFM_S::NIFM_S() : ServiceFramework("nifm:s") {
RegisterHandlers(functions);
}
-} // namespace NIFM
-} // namespace Service
+} // namespace Service::NIFM
diff --git a/src/core/hle/service/nifm/nifm_s.h b/src/core/hle/service/nifm/nifm_s.h
index d11a1ec29..8d1635a5d 100644
--- a/src/core/hle/service/nifm/nifm_s.h
+++ b/src/core/hle/service/nifm/nifm_s.h
@@ -4,21 +4,13 @@
#pragma once
-#include "core/hle/kernel/hle_ipc.h"
-#include "core/hle/service/service.h"
+#include "core/hle/service/nifm/nifm.h"
-namespace Service {
-namespace NIFM {
+namespace Service::NIFM {
-class NIFM_S final : public ServiceFramework<NIFM_S> {
+class NIFM_S final : public Module::Interface {
public:
- NIFM_S();
- ~NIFM_S() = default;
-
-private:
- void CreateGeneralServiceOld(Kernel::HLERequestContext& ctx);
- void CreateGeneralService(Kernel::HLERequestContext& ctx);
+ explicit NIFM_S(std::shared_ptr<Module> module);
};
-} // namespace NIFM
-} // namespace Service
+} // namespace Service::NIFM
diff --git a/src/core/hle/service/nifm/nifm_u.cpp b/src/core/hle/service/nifm/nifm_u.cpp
index a5895c13c..8cb75b903 100644
--- a/src/core/hle/service/nifm/nifm_u.cpp
+++ b/src/core/hle/service/nifm/nifm_u.cpp
@@ -2,29 +2,11 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include "common/logging/log.h"
-#include "core/hle/ipc_helpers.h"
-#include "core/hle/service/nifm/nifm.h"
#include "core/hle/service/nifm/nifm_u.h"
-namespace Service {
-namespace NIFM {
+namespace Service::NIFM {
-void NIFM_U::CreateGeneralServiceOld(Kernel::HLERequestContext& ctx) {
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IGeneralService>();
- LOG_DEBUG(Service_NIFM, "called");
-}
-
-void NIFM_U::CreateGeneralService(Kernel::HLERequestContext& ctx) {
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IGeneralService>();
- LOG_DEBUG(Service_NIFM, "called");
-}
-
-NIFM_U::NIFM_U() : ServiceFramework("nifm:u") {
+NIFM_U::NIFM_U(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "nifm:u") {
static const FunctionInfo functions[] = {
{4, &NIFM_U::CreateGeneralServiceOld, "CreateGeneralServiceOld"},
{5, &NIFM_U::CreateGeneralService, "CreateGeneralService"},
@@ -32,5 +14,4 @@ NIFM_U::NIFM_U() : ServiceFramework("nifm:u") {
RegisterHandlers(functions);
}
-} // namespace NIFM
-} // namespace Service
+} // namespace Service::NIFM
diff --git a/src/core/hle/service/nifm/nifm_u.h b/src/core/hle/service/nifm/nifm_u.h
index da40b604f..def9726b1 100644
--- a/src/core/hle/service/nifm/nifm_u.h
+++ b/src/core/hle/service/nifm/nifm_u.h
@@ -4,21 +4,13 @@
#pragma once
-#include "core/hle/kernel/hle_ipc.h"
-#include "core/hle/service/service.h"
+#include "core/hle/service/nifm/nifm.h"
-namespace Service {
-namespace NIFM {
+namespace Service::NIFM {
-class NIFM_U final : public ServiceFramework<NIFM_U> {
+class NIFM_U final : public Module::Interface {
public:
- NIFM_U();
- ~NIFM_U() = default;
-
-private:
- void CreateGeneralServiceOld(Kernel::HLERequestContext& ctx);
- void CreateGeneralService(Kernel::HLERequestContext& ctx);
+ explicit NIFM_U(std::shared_ptr<Module> module);
};
-} // namespace NIFM
-} // namespace Service
+} // namespace Service::NIFM
diff --git a/src/core/hle/service/ns/ns.cpp b/src/core/hle/service/ns/ns.cpp
index 45681c50f..89c703310 100644
--- a/src/core/hle/service/ns/ns.cpp
+++ b/src/core/hle/service/ns/ns.cpp
@@ -5,12 +5,10 @@
#include "core/hle/service/ns/ns.h"
#include "core/hle/service/ns/pl_u.h"
-namespace Service {
-namespace NS {
+namespace Service::NS {
void InstallInterfaces(SM::ServiceManager& service_manager) {
std::make_shared<PL_U>()->InstallAsService(service_manager);
}
-} // namespace NS
-} // namespace Service
+} // namespace Service::NS
diff --git a/src/core/hle/service/ns/ns.h b/src/core/hle/service/ns/ns.h
index a4b7e3ded..b81ca8f1e 100644
--- a/src/core/hle/service/ns/ns.h
+++ b/src/core/hle/service/ns/ns.h
@@ -6,11 +6,9 @@
#include "core/hle/service/service.h"
-namespace Service {
-namespace NS {
+namespace Service::NS {
/// Registers all NS services with the specified service manager.
void InstallInterfaces(SM::ServiceManager& service_manager);
-} // namespace NS
-} // namespace Service
+} // namespace Service::NS
diff --git a/src/core/hle/service/ns/pl_u.cpp b/src/core/hle/service/ns/pl_u.cpp
index cc9d03a7c..636af9a1e 100644
--- a/src/core/hle/service/ns/pl_u.cpp
+++ b/src/core/hle/service/ns/pl_u.cpp
@@ -4,11 +4,11 @@
#include "common/common_paths.h"
#include "common/file_util.h"
+#include "core/core.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/service/ns/pl_u.h"
-namespace Service {
-namespace NS {
+namespace Service::NS {
struct FontRegion {
u32 offset;
@@ -32,10 +32,13 @@ enum class LoadState : u32 {
PL_U::PL_U() : ServiceFramework("pl:u") {
static const FunctionInfo functions[] = {
+ {0, &PL_U::RequestLoad, "RequestLoad"},
{1, &PL_U::GetLoadState, "GetLoadState"},
{2, &PL_U::GetSize, "GetSize"},
{3, &PL_U::GetSharedMemoryAddressOffset, "GetSharedMemoryAddressOffset"},
- {4, &PL_U::GetSharedMemoryNativeHandle, "GetSharedMemoryNativeHandle"}};
+ {4, &PL_U::GetSharedMemoryNativeHandle, "GetSharedMemoryNativeHandle"},
+ {5, &PL_U::GetSharedFontInOrderOfPriority, "GetSharedFontInOrderOfPriority"},
+ };
RegisterHandlers(functions);
// Attempt to load shared font data from disk
@@ -43,21 +46,30 @@ PL_U::PL_U() : ServiceFramework("pl:u") {
FileUtil::CreateFullPath(filepath); // Create path if not already created
FileUtil::IOFile file(filepath, "rb");
+ shared_font = std::make_shared<std::vector<u8>>(SHARED_FONT_MEM_SIZE);
if (file.IsOpen()) {
// Read shared font data
ASSERT(file.GetSize() == SHARED_FONT_MEM_SIZE);
- shared_font = std::make_shared<std::vector<u8>>(static_cast<size_t>(file.GetSize()));
file.ReadBytes(shared_font->data(), shared_font->size());
} else {
- LOG_WARNING(Service_NS, "Unable to load shared font: %s", filepath.c_str());
+ NGLOG_WARNING(Service_NS, "Unable to load shared font: {}", filepath);
}
}
+void PL_U::RequestLoad(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const u32 shared_font_type{rp.Pop<u32>()};
+
+ NGLOG_DEBUG(Service_NS, "called, shared_font_type={}", shared_font_type);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
void PL_U::GetLoadState(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const u32 font_id{rp.Pop<u32>()};
- LOG_DEBUG(Service_NS, "called, font_id=%d", font_id);
+ NGLOG_DEBUG(Service_NS, "called, font_id={}", font_id);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(static_cast<u32>(LoadState::Done));
@@ -67,7 +79,7 @@ void PL_U::GetSize(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const u32 font_id{rp.Pop<u32>()};
- LOG_DEBUG(Service_NS, "called, font_id=%d", font_id);
+ NGLOG_DEBUG(Service_NS, "called, font_id={}", font_id);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(SHARED_FONT_REGIONS[font_id].size);
@@ -77,35 +89,56 @@ void PL_U::GetSharedMemoryAddressOffset(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const u32 font_id{rp.Pop<u32>()};
- LOG_DEBUG(Service_NS, "called, font_id=%d", font_id);
+ NGLOG_DEBUG(Service_NS, "called, font_id={}", font_id);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(SHARED_FONT_REGIONS[font_id].offset);
}
void PL_U::GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx) {
- if (shared_font != nullptr) {
- // TODO(bunnei): This is a less-than-ideal solution to load a RAM dump of the Switch shared
- // font data. This (likely) relies on exact address, size, and offsets from the original
- // dump. In the future, we need to replace this with a more robust solution.
-
- // Map backing memory for the font data
- Kernel::g_current_process->vm_manager.MapMemoryBlock(SHARED_FONT_MEM_VADDR, shared_font, 0,
- SHARED_FONT_MEM_SIZE,
- Kernel::MemoryState::Shared);
-
- // Create shared font memory object
- shared_font_mem = Kernel::SharedMemory::Create(
- Kernel::g_current_process, SHARED_FONT_MEM_SIZE, Kernel::MemoryPermission::ReadWrite,
- Kernel::MemoryPermission::Read, SHARED_FONT_MEM_VADDR, Kernel::MemoryRegion::BASE,
- "PL_U:shared_font_mem");
- }
+ // TODO(bunnei): This is a less-than-ideal solution to load a RAM dump of the Switch shared
+ // font data. This (likely) relies on exact address, size, and offsets from the original
+ // dump. In the future, we need to replace this with a more robust solution.
+
+ // Map backing memory for the font data
+ Core::CurrentProcess()->vm_manager.MapMemoryBlock(
+ SHARED_FONT_MEM_VADDR, shared_font, 0, SHARED_FONT_MEM_SIZE, Kernel::MemoryState::Shared);
+
+ // Create shared font memory object
+ shared_font_mem = Kernel::SharedMemory::Create(
+ Core::CurrentProcess(), SHARED_FONT_MEM_SIZE, Kernel::MemoryPermission::ReadWrite,
+ Kernel::MemoryPermission::Read, SHARED_FONT_MEM_VADDR, Kernel::MemoryRegion::BASE,
+ "PL_U:shared_font_mem");
- LOG_DEBUG(Service_NS, "called");
+ NGLOG_DEBUG(Service_NS, "called");
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
rb.PushCopyObjects(shared_font_mem);
}
-} // namespace NS
-} // namespace Service
+void PL_U::GetSharedFontInOrderOfPriority(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const u64 language_code{rp.Pop<u64>()}; // TODO(ogniK): Find out what this is used for
+ NGLOG_DEBUG(Service_NS, "called, language_code=%lx", language_code);
+ IPC::ResponseBuilder rb{ctx, 4};
+ std::vector<u32> font_codes;
+ std::vector<u32> font_offsets;
+ std::vector<u32> font_sizes;
+
+ // TODO(ogniK): Have actual priority order
+ for (size_t i = 0; i < SHARED_FONT_REGIONS.size(); i++) {
+ font_codes.push_back(static_cast<u32>(i));
+ font_offsets.push_back(SHARED_FONT_REGIONS[i].offset);
+ font_sizes.push_back(SHARED_FONT_REGIONS[i].size);
+ }
+
+ ctx.WriteBuffer(font_codes.data(), font_codes.size(), 0);
+ ctx.WriteBuffer(font_offsets.data(), font_offsets.size(), 1);
+ ctx.WriteBuffer(font_sizes.data(), font_sizes.size(), 2);
+
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u8>(static_cast<u8>(LoadState::Done)); // Fonts Loaded
+ rb.Push<u32>(static_cast<u32>(font_codes.size()));
+}
+
+} // namespace Service::NS
diff --git a/src/core/hle/service/ns/pl_u.h b/src/core/hle/service/ns/pl_u.h
index 7a4766338..fcc2acab7 100644
--- a/src/core/hle/service/ns/pl_u.h
+++ b/src/core/hle/service/ns/pl_u.h
@@ -8,8 +8,7 @@
#include "core/hle/kernel/shared_memory.h"
#include "core/hle/service/service.h"
-namespace Service {
-namespace NS {
+namespace Service::NS {
class PL_U final : public ServiceFramework<PL_U> {
public:
@@ -17,10 +16,12 @@ public:
~PL_U() = default;
private:
+ void RequestLoad(Kernel::HLERequestContext& ctx);
void GetLoadState(Kernel::HLERequestContext& ctx);
void GetSize(Kernel::HLERequestContext& ctx);
void GetSharedMemoryAddressOffset(Kernel::HLERequestContext& ctx);
void GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx);
+ void GetSharedFontInOrderOfPriority(Kernel::HLERequestContext& ctx);
/// Handle to shared memory region designated for a shared font
Kernel::SharedPtr<Kernel::SharedMemory> shared_font_mem;
@@ -29,5 +30,4 @@ private:
std::shared_ptr<std::vector<u8>> shared_font;
};
-} // namespace NS
-} // namespace Service
+} // namespace Service::NS
diff --git a/src/core/hle/service/nvdrv/devices/nvdevice.h b/src/core/hle/service/nvdrv/devices/nvdevice.h
index cdc25b059..0f02a1a18 100644
--- a/src/core/hle/service/nvdrv/devices/nvdevice.h
+++ b/src/core/hle/service/nvdrv/devices/nvdevice.h
@@ -9,9 +9,7 @@
#include "common/common_types.h"
#include "common/swap.h"
-namespace Service {
-namespace Nvidia {
-namespace Devices {
+namespace Service::Nvidia::Devices {
/// Represents an abstract nvidia device node. It is to be subclassed by concrete device nodes to
/// implement the ioctl interface.
@@ -38,6 +36,4 @@ public:
virtual u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) = 0;
};
-} // namespace Devices
-} // namespace Nvidia
-} // namespace Service
+} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp
index 7674d332d..103e66d0c 100644
--- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp
@@ -10,32 +10,27 @@
#include "video_core/renderer_base.h"
#include "video_core/video_core.h"
-namespace Service {
-namespace Nvidia {
-namespace Devices {
+namespace Service::Nvidia::Devices {
u32 nvdisp_disp0::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) {
- UNIMPLEMENTED();
+ UNIMPLEMENTED_MSG("Unimplemented ioctl");
return 0;
}
void nvdisp_disp0::flip(u32 buffer_handle, u32 offset, u32 format, u32 width, u32 height,
u32 stride, NVFlinger::BufferQueue::BufferTransformFlags transform) {
VAddr addr = nvmap_dev->GetObjectAddress(buffer_handle);
- LOG_WARNING(Service,
- "Drawing from address %llx offset %08X Width %u Height %u Stride %u Format %u",
- addr, offset, width, height, stride, format);
+ NGLOG_WARNING(Service,
+ "Drawing from address {:X} offset {:08X} Width {} Height {} Stride {} Format {}",
+ addr, offset, width, height, stride, format);
- using PixelFormat = RendererBase::FramebufferInfo::PixelFormat;
- using Flags = NVFlinger::BufferQueue::BufferTransformFlags;
- const bool flip_vertical = static_cast<u32>(transform) & static_cast<u32>(Flags::FlipV);
- const RendererBase::FramebufferInfo framebuffer_info{
- addr, offset, width, height, stride, static_cast<PixelFormat>(format), flip_vertical};
+ using PixelFormat = Tegra::FramebufferConfig::PixelFormat;
+ const Tegra::FramebufferConfig framebuffer{
+ addr, offset, width, height, stride, static_cast<PixelFormat>(format), transform};
Core::System::GetInstance().perf_stats.EndGameFrame();
- VideoCore::g_renderer->SwapBuffers(framebuffer_info);
+
+ VideoCore::g_renderer->SwapBuffers(framebuffer);
}
-} // namespace Devices
-} // namespace Nvidia
-} // namespace Service
+} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h
index 66f56f23d..3d3979723 100644
--- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h
+++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h
@@ -10,9 +10,7 @@
#include "core/hle/service/nvdrv/devices/nvdevice.h"
#include "core/hle/service/nvflinger/buffer_queue.h"
-namespace Service {
-namespace Nvidia {
-namespace Devices {
+namespace Service::Nvidia::Devices {
class nvmap;
@@ -31,6 +29,4 @@ private:
std::shared_ptr<nvmap> nvmap_dev;
};
-} // namespace Devices
-} // namespace Nvidia
-} // namespace Service
+} // namespace Service::Nvidia::Devices
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 9892402fa..c1eea861d 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
@@ -9,13 +9,11 @@
#include "core/hle/service/nvdrv/devices/nvhost_as_gpu.h"
#include "core/hle/service/nvdrv/devices/nvmap.h"
-namespace Service {
-namespace Nvidia {
-namespace Devices {
+namespace Service::Nvidia::Devices {
u32 nvhost_as_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) {
- LOG_DEBUG(Service_NVDRV, "called, command=0x%08x, input_size=0x%zx, output_size=0x%zx",
- command.raw, input.size(), output.size());
+ NGLOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}",
+ command.raw, input.size(), output.size());
switch (static_cast<IoctlCommand>(command.raw)) {
case IoctlCommand::IocInitalizeExCommand:
@@ -28,23 +26,29 @@ u32 nvhost_as_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vecto
return BindChannel(input, output);
case IoctlCommand::IocGetVaRegionsCommand:
return GetVARegions(input, output);
+ case IoctlCommand::IocUnmapBufferCommand:
+ return UnmapBuffer(input, output);
}
+
+ if (static_cast<IoctlCommand>(command.cmd.Value()) == IoctlCommand::IocRemapCommand)
+ return Remap(input, output);
+
+ UNIMPLEMENTED_MSG("Unimplemented ioctl command");
return 0;
}
u32 nvhost_as_gpu::InitalizeEx(const std::vector<u8>& input, std::vector<u8>& output) {
IoctlInitalizeEx params{};
std::memcpy(&params, input.data(), input.size());
- LOG_WARNING(Service_NVDRV, "(STUBBED) called, big_page_size=0x%x", params.big_page_size);
- std::memcpy(output.data(), &params, output.size());
+ NGLOG_WARNING(Service_NVDRV, "(STUBBED) called, big_page_size=0x{:X}", params.big_page_size);
return 0;
}
u32 nvhost_as_gpu::AllocateSpace(const std::vector<u8>& input, std::vector<u8>& output) {
IoctlAllocSpace params{};
std::memcpy(&params, input.data(), input.size());
- LOG_DEBUG(Service_NVDRV, "called, pages=%x, page_size=%x, flags=%x", params.pages,
- params.page_size, params.flags);
+ NGLOG_DEBUG(Service_NVDRV, "called, pages={:X}, page_size={:X}, flags={:X}", params.pages,
+ params.page_size, params.flags);
auto& gpu = Core::System::GetInstance().GPU();
const u64 size{static_cast<u64>(params.pages) * static_cast<u64>(params.page_size)};
@@ -58,15 +62,45 @@ u32 nvhost_as_gpu::AllocateSpace(const std::vector<u8>& input, std::vector<u8>&
return 0;
}
+u32 nvhost_as_gpu::Remap(const std::vector<u8>& input, std::vector<u8>& output) {
+ size_t num_entries = input.size() / sizeof(IoctlRemapEntry);
+
+ NGLOG_WARNING(Service_NVDRV, "(STUBBED) called, num_entries=0x{:X}", num_entries);
+
+ std::vector<IoctlRemapEntry> entries(num_entries);
+ std::memcpy(entries.data(), input.data(), input.size());
+
+ auto& gpu = Core::System::GetInstance().GPU();
+
+ for (const auto& entry : entries) {
+ NGLOG_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;
+
+ auto object = nvmap_dev->GetObject(entry.nvmap_handle);
+ ASSERT(object);
+
+ ASSERT(object->status == nvmap::Object::Status::Allocated);
+
+ u64 size = static_cast<u64>(entry.pages) << 0x10;
+ ASSERT(size <= object->size);
+
+ Tegra::GPUVAddr returned = gpu.memory_manager->MapBufferEx(object->addr, offset, size);
+ ASSERT(returned == offset);
+ }
+ std::memcpy(output.data(), entries.data(), output.size());
+ return 0;
+}
+
u32 nvhost_as_gpu::MapBufferEx(const std::vector<u8>& input, std::vector<u8>& output) {
IoctlMapBufferEx params{};
std::memcpy(&params, input.data(), input.size());
- LOG_DEBUG(Service_NVDRV,
- "called, flags=%x, nvmap_handle=%x, buffer_offset=%" PRIu64 ", mapping_size=%" PRIu64
- ", offset=%" PRIu64,
- params.flags, params.nvmap_handle, params.buffer_offset, params.mapping_size,
- params.offset);
+ NGLOG_DEBUG(Service_NVDRV,
+ "called, flags={:X}, nvmap_handle={:X}, buffer_offset={}, mapping_size={}"
+ ", offset={}",
+ params.flags, params.nvmap_handle, params.buffer_offset, params.mapping_size,
+ params.offset);
if (!params.nvmap_handle) {
return 0;
@@ -75,6 +109,16 @@ u32 nvhost_as_gpu::MapBufferEx(const std::vector<u8>& input, std::vector<u8>& ou
auto object = nvmap_dev->GetObject(params.nvmap_handle);
ASSERT(object);
+ // We can only map objects that have already been assigned a CPU address.
+ ASSERT(object->status == nvmap::Object::Status::Allocated);
+
+ ASSERT(params.buffer_offset == 0);
+
+ // The real nvservices doesn't make a distinction between handles and ids, and
+ // object can only have one handle and it will be the same as its id. Assert that this is the
+ // case to prevent unexpected behavior.
+ ASSERT(object->id == params.nvmap_handle);
+
auto& gpu = Core::System::GetInstance().GPU();
if (params.flags & 1) {
@@ -83,6 +127,37 @@ u32 nvhost_as_gpu::MapBufferEx(const std::vector<u8>& input, std::vector<u8>& ou
params.offset = gpu.memory_manager->MapBufferEx(object->addr, object->size);
}
+ // Create a new mapping entry for this operation.
+ ASSERT_MSG(buffer_mappings.find(params.offset) == buffer_mappings.end(),
+ "Offset is already mapped");
+
+ BufferMapping mapping{};
+ mapping.nvmap_handle = params.nvmap_handle;
+ mapping.offset = params.offset;
+ mapping.size = object->size;
+
+ buffer_mappings[params.offset] = mapping;
+
+ std::memcpy(output.data(), &params, output.size());
+ return 0;
+}
+
+u32 nvhost_as_gpu::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output) {
+ IoctlUnmapBuffer params{};
+ std::memcpy(&params, input.data(), input.size());
+
+ NGLOG_DEBUG(Service_NVDRV, "called, offset=0x{:X}", params.offset);
+
+ auto& gpu = Core::System::GetInstance().GPU();
+
+ auto itr = buffer_mappings.find(params.offset);
+
+ ASSERT_MSG(itr != buffer_mappings.end(), "Tried to unmap invalid mapping");
+
+ params.offset = gpu.memory_manager->UnmapBuffer(params.offset, itr->second.size);
+
+ buffer_mappings.erase(itr->second.offset);
+
std::memcpy(output.data(), &params, output.size());
return 0;
}
@@ -90,17 +165,16 @@ u32 nvhost_as_gpu::MapBufferEx(const std::vector<u8>& input, std::vector<u8>& ou
u32 nvhost_as_gpu::BindChannel(const std::vector<u8>& input, std::vector<u8>& output) {
IoctlBindChannel params{};
std::memcpy(&params, input.data(), input.size());
- LOG_DEBUG(Service_NVDRV, "called, fd=%x", params.fd);
+ NGLOG_DEBUG(Service_NVDRV, "called, fd={:X}", params.fd);
channel = params.fd;
- std::memcpy(output.data(), &params, output.size());
return 0;
}
u32 nvhost_as_gpu::GetVARegions(const std::vector<u8>& input, std::vector<u8>& output) {
IoctlGetVaRegions params{};
std::memcpy(&params, input.data(), input.size());
- LOG_WARNING(Service_NVDRV, "(STUBBED) called, buf_addr=%" PRIu64 ", buf_size=%x",
- params.buf_addr, params.buf_size);
+ NGLOG_WARNING(Service_NVDRV, "(STUBBED) called, buf_addr={:X}, buf_size={:X}", params.buf_addr,
+ params.buf_size);
params.buf_size = 0x30;
params.regions[0].offset = 0x04000000;
@@ -115,6 +189,4 @@ u32 nvhost_as_gpu::GetVARegions(const std::vector<u8>& input, std::vector<u8>& o
return 0;
}
-} // namespace Devices
-} // namespace Nvidia
-} // namespace Service
+} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h
index f8a60cce7..d4c4b4db3 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h
@@ -5,15 +5,14 @@
#pragma once
#include <memory>
+#include <unordered_map>
#include <utility>
#include <vector>
#include "common/common_types.h"
#include "common/swap.h"
#include "core/hle/service/nvdrv/devices/nvdevice.h"
-namespace Service {
-namespace Nvidia {
-namespace Devices {
+namespace Service::Nvidia::Devices {
class nvmap;
@@ -28,9 +27,11 @@ private:
enum class IoctlCommand : u32_le {
IocInitalizeExCommand = 0x40284109,
IocAllocateSpaceCommand = 0xC0184102,
+ IocRemapCommand = 0x00000014,
IocMapBufferExCommand = 0xC0284106,
IocBindChannelCommand = 0x40044101,
IocGetVaRegionsCommand = 0xC0404108,
+ IocUnmapBufferCommand = 0xC0084105,
};
struct IoctlInitalizeEx {
@@ -56,6 +57,16 @@ private:
};
static_assert(sizeof(IoctlAllocSpace) == 24, "IoctlInitalizeEx is incorrect size");
+ struct IoctlRemapEntry {
+ u16_le flags;
+ u16_le kind;
+ u32_le nvmap_handle;
+ INSERT_PADDING_WORDS(1);
+ u32_le offset;
+ u32_le pages;
+ };
+ static_assert(sizeof(IoctlRemapEntry) == 20, "IoctlRemapEntry is incorrect size");
+
struct IoctlMapBufferEx {
u32_le flags; // bit0: fixed_offset, bit2: cacheable
u32_le kind; // -1 is default
@@ -67,6 +78,11 @@ private:
};
static_assert(sizeof(IoctlMapBufferEx) == 40, "IoctlMapBufferEx is incorrect size");
+ struct IoctlUnmapBuffer {
+ u64_le offset;
+ };
+ static_assert(sizeof(IoctlUnmapBuffer) == 8, "IoctlUnmapBuffer is incorrect size");
+
struct IoctlBindChannel {
u32_le fd;
};
@@ -89,17 +105,26 @@ private:
static_assert(sizeof(IoctlGetVaRegions) == 16 + sizeof(IoctlVaRegion) * 2,
"IoctlGetVaRegions is incorrect size");
+ struct BufferMapping {
+ u64 offset;
+ u64 size;
+ u32 nvmap_handle;
+ };
+
+ /// Map containing the nvmap object mappings in GPU memory.
+ std::unordered_map<u64, BufferMapping> buffer_mappings;
+
u32 channel{};
u32 InitalizeEx(const std::vector<u8>& input, std::vector<u8>& output);
u32 AllocateSpace(const std::vector<u8>& input, std::vector<u8>& output);
+ u32 Remap(const std::vector<u8>& input, std::vector<u8>& output);
u32 MapBufferEx(const std::vector<u8>& input, std::vector<u8>& output);
+ u32 UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output);
u32 BindChannel(const std::vector<u8>& input, std::vector<u8>& output);
u32 GetVARegions(const std::vector<u8>& input, std::vector<u8>& output);
std::shared_ptr<nvmap> nvmap_dev;
};
-} // namespace Devices
-} // namespace Nvidia
-} // namespace Service
+} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp
index 45711d686..7872d1e09 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp
@@ -6,29 +6,31 @@
#include "common/logging/log.h"
#include "core/hle/service/nvdrv/devices/nvhost_ctrl.h"
-namespace Service {
-namespace Nvidia {
-namespace Devices {
+namespace Service::Nvidia::Devices {
u32 nvhost_ctrl::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) {
- LOG_DEBUG(Service_NVDRV, "called, command=0x%08x, input_size=0x%zx, output_size=0x%zx",
- command.raw, input.size(), output.size());
+ NGLOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}",
+ command.raw, input.size(), output.size());
switch (static_cast<IoctlCommand>(command.raw)) {
case IoctlCommand::IocGetConfigCommand:
return NvOsGetConfigU32(input, output);
case IoctlCommand::IocCtrlEventWaitCommand:
- return IocCtrlEventWait(input, output);
+ return IocCtrlEventWait(input, output, false);
+ case IoctlCommand::IocCtrlEventWaitAsyncCommand:
+ return IocCtrlEventWait(input, output, true);
+ case IoctlCommand::IocCtrlEventRegisterCommand:
+ return IocCtrlEventRegister(input, output);
}
- UNIMPLEMENTED();
+ UNIMPLEMENTED_MSG("Unimplemented ioctl");
return 0;
}
u32 nvhost_ctrl::NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>& output) {
IocGetConfigParams params{};
std::memcpy(&params, input.data(), sizeof(params));
- LOG_DEBUG(Service_NVDRV, "called, setting=%s!%s", params.domain_str.data(),
- params.param_str.data());
+ NGLOG_DEBUG(Service_NVDRV, "called, setting={}!{}", params.domain_str.data(),
+ params.param_str.data());
if (!strcmp(params.domain_str.data(), "nv")) {
if (!strcmp(params.param_str.data(), "NV_MEMORY_PROFILER")) {
@@ -38,7 +40,7 @@ u32 nvhost_ctrl::NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>&
} else if (!strcmp(params.param_str.data(), "NVRM_GPU_PREVENT_USE")) {
params.config_str[0] = '0';
} else {
- params.config_str[0] = '0';
+ params.config_str[0] = '\0';
}
} else {
UNIMPLEMENTED(); // unknown domain? Only nv has been seen so far on hardware
@@ -47,11 +49,13 @@ u32 nvhost_ctrl::NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>&
return 0;
}
-u32 nvhost_ctrl::IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output) {
+u32 nvhost_ctrl::IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output,
+ bool is_async) {
IocCtrlEventWaitParams params{};
std::memcpy(&params, input.data(), sizeof(params));
- LOG_WARNING(Service_NVDRV, "(STUBBED) called, syncpt_id=%u threshold=%u timeout=%d",
- params.syncpt_id, params.threshold, params.timeout);
+ NGLOG_WARNING(Service_NVDRV,
+ "(STUBBED) called, syncpt_id={}, threshold={}, timeout={}, is_async={}",
+ params.syncpt_id, params.threshold, params.timeout, is_async);
// TODO(Subv): Implement actual syncpt waiting.
params.value = 0;
@@ -59,6 +63,10 @@ u32 nvhost_ctrl::IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>&
return 0;
}
-} // namespace Devices
-} // namespace Nvidia
-} // namespace Service
+u32 nvhost_ctrl::IocCtrlEventRegister(const std::vector<u8>& input, std::vector<u8>& output) {
+ NGLOG_WARNING(Service_NVDRV, "(STUBBED) called");
+ // TODO(bunnei): Implement this.
+ return 0;
+}
+
+} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h
index 0ca01aa6d..090261a60 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h
@@ -11,9 +11,7 @@
#include "common/common_types.h"
#include "core/hle/service/nvdrv/devices/nvdevice.h"
-namespace Service {
-namespace Nvidia {
-namespace Devices {
+namespace Service::Nvidia::Devices {
class nvhost_ctrl final : public nvdevice {
public:
@@ -28,12 +26,64 @@ private:
IocSyncptIncrCommand = 0x40040015,
IocSyncptWaitCommand = 0xC00C0016,
IocModuleMutexCommand = 0x40080017,
- IocModuleRegRDWRCommand = 0xC008010E,
+ IocModuleRegRDWRCommand = 0xC0180018,
IocSyncptWaitexCommand = 0xC0100019,
IocSyncptReadMaxCommand = 0xC008001A,
- IocCtrlEventWaitCommand = 0xC010001D,
IocGetConfigCommand = 0xC183001B,
+ IocCtrlEventSignalCommand = 0xC004001C,
+ IocCtrlEventWaitCommand = 0xC010001D,
+ IocCtrlEventWaitAsyncCommand = 0xC010001E,
+ IocCtrlEventRegisterCommand = 0xC004001F,
+ IocCtrlEventUnregisterCommand = 0xC0040020,
+ IocCtrlEventKillCommand = 0x40080021,
+ };
+ struct IocSyncptReadParams {
+ u32_le id;
+ u32_le value;
+ };
+ static_assert(sizeof(IocSyncptReadParams) == 8, "IocSyncptReadParams is incorrect size");
+
+ struct IocSyncptIncrParams {
+ u32_le id;
+ };
+ static_assert(sizeof(IocSyncptIncrParams) == 4, "IocSyncptIncrParams is incorrect size");
+
+ struct IocSyncptWaitParams {
+ u32_le id;
+ u32_le thresh;
+ s32_le timeout;
+ };
+ static_assert(sizeof(IocSyncptWaitParams) == 12, "IocSyncptWaitParams is incorrect size");
+
+ struct IocModuleMutexParams {
+ u32_le id;
+ u32_le lock; // (0 = unlock and 1 = lock)
+ };
+ static_assert(sizeof(IocModuleMutexParams) == 8, "IocModuleMutexParams is incorrect size");
+
+ struct IocModuleRegRDWRParams {
+ u32_le id;
+ u32_le num_offsets;
+ u32_le block_size;
+ u32_le offsets;
+ u32_le values;
+ u32_le write;
+ };
+ static_assert(sizeof(IocModuleRegRDWRParams) == 24, "IocModuleRegRDWRParams is incorrect size");
+
+ struct IocSyncptWaitexParams {
+ u32_le id;
+ u32_le thresh;
+ s32_le timeout;
+ u32_le value;
};
+ static_assert(sizeof(IocSyncptWaitexParams) == 16, "IocSyncptWaitexParams is incorrect size");
+
+ struct IocSyncptReadMaxParams {
+ u32_le id;
+ u32_le value;
+ };
+ static_assert(sizeof(IocSyncptReadMaxParams) == 8, "IocSyncptReadMaxParams is incorrect size");
struct IocGetConfigParams {
std::array<char, 0x41> domain_str;
@@ -42,6 +92,12 @@ private:
};
static_assert(sizeof(IocGetConfigParams) == 387, "IocGetConfigParams is incorrect size");
+ struct IocCtrlEventSignalParams {
+ u32_le user_event_id;
+ };
+ static_assert(sizeof(IocCtrlEventSignalParams) == 4,
+ "IocCtrlEventSignalParams is incorrect size");
+
struct IocCtrlEventWaitParams {
u32_le syncpt_id;
u32_le threshold;
@@ -50,11 +106,37 @@ private:
};
static_assert(sizeof(IocCtrlEventWaitParams) == 16, "IocCtrlEventWaitParams is incorrect size");
+ struct IocCtrlEventWaitAsyncParams {
+ u32_le syncpt_id;
+ u32_le threshold;
+ u32_le timeout;
+ u32_le value;
+ };
+ static_assert(sizeof(IocCtrlEventWaitAsyncParams) == 16,
+ "IocCtrlEventWaitAsyncParams is incorrect size");
+
+ struct IocCtrlEventRegisterParams {
+ u32_le user_event_id;
+ };
+ static_assert(sizeof(IocCtrlEventRegisterParams) == 4,
+ "IocCtrlEventRegisterParams is incorrect size");
+
+ struct IocCtrlEventUnregisterParams {
+ u32_le user_event_id;
+ };
+ static_assert(sizeof(IocCtrlEventUnregisterParams) == 4,
+ "IocCtrlEventUnregisterParams is incorrect size");
+
+ struct IocCtrlEventKill {
+ u64_le user_events;
+ };
+ static_assert(sizeof(IocCtrlEventKill) == 8, "IocCtrlEventKill is incorrect size");
+
u32 NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>& output);
- u32 IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output);
+ u32 IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output, bool is_async);
+
+ u32 IocCtrlEventRegister(const std::vector<u8>& input, std::vector<u8>& output);
};
-} // namespace Devices
-} // namespace Nvidia
-} // namespace Service
+} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp
index 3b353d742..0abc0de83 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp
@@ -7,13 +7,11 @@
#include "common/logging/log.h"
#include "core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h"
-namespace Service {
-namespace Nvidia {
-namespace Devices {
+namespace Service::Nvidia::Devices {
u32 nvhost_ctrl_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) {
- LOG_DEBUG(Service_NVDRV, "called, command=0x%08x, input_size=0x%zx, output_size=0x%zx",
- command.raw, input.size(), output.size());
+ NGLOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}",
+ command.raw, input.size(), output.size());
switch (static_cast<IoctlCommand>(command.raw)) {
case IoctlCommand::IocGetCharacteristicsCommand:
@@ -26,13 +24,19 @@ u32 nvhost_ctrl_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vec
return ZCullGetCtxSize(input, output);
case IoctlCommand::IocZcullGetInfo:
return ZCullGetInfo(input, output);
+ case IoctlCommand::IocZbcSetTable:
+ return ZBCSetTable(input, output);
+ case IoctlCommand::IocZbcQueryTable:
+ return ZBCQueryTable(input, output);
+ case IoctlCommand::IocFlushL2:
+ return FlushL2(input, output);
}
- UNIMPLEMENTED();
+ UNIMPLEMENTED_MSG("Unimplemented ioctl");
return 0;
}
u32 nvhost_ctrl_gpu::GetCharacteristics(const std::vector<u8>& input, std::vector<u8>& output) {
- LOG_DEBUG(Service_NVDRV, "called");
+ NGLOG_DEBUG(Service_NVDRV, "called");
IoctlCharacteristics params{};
std::memcpy(&params, input.data(), input.size());
params.gc.arch = 0x120;
@@ -79,14 +83,19 @@ u32 nvhost_ctrl_gpu::GetCharacteristics(const std::vector<u8>& input, std::vecto
u32 nvhost_ctrl_gpu::GetTPCMasks(const std::vector<u8>& input, std::vector<u8>& output) {
IoctlGpuGetTpcMasksArgs params{};
std::memcpy(&params, input.data(), input.size());
- LOG_WARNING(Service_NVDRV, "(STUBBED) called, mask=0x%x, mask_buf_addr=0x%" PRIx64,
- params.mask_buf_size, params.mask_buf_addr);
+ NGLOG_INFO(Service_NVDRV, "called, mask=0x{:X}, mask_buf_addr=0x{:X}", params.mask_buf_size,
+ params.mask_buf_addr);
+ // TODO(ogniK): Confirm value on hardware
+ if (params.mask_buf_size)
+ params.tpc_mask_size = 4 * 1; // 4 * num_gpc
+ else
+ params.tpc_mask_size = 0;
std::memcpy(output.data(), &params, sizeof(params));
return 0;
}
u32 nvhost_ctrl_gpu::GetActiveSlotMask(const std::vector<u8>& input, std::vector<u8>& output) {
- LOG_DEBUG(Service_NVDRV, "called");
+ NGLOG_DEBUG(Service_NVDRV, "called");
IoctlActiveSlotMask params{};
std::memcpy(&params, input.data(), input.size());
params.slot = 0x07;
@@ -96,7 +105,7 @@ u32 nvhost_ctrl_gpu::GetActiveSlotMask(const std::vector<u8>& input, std::vector
}
u32 nvhost_ctrl_gpu::ZCullGetCtxSize(const std::vector<u8>& input, std::vector<u8>& output) {
- LOG_DEBUG(Service_NVDRV, "called");
+ NGLOG_DEBUG(Service_NVDRV, "called");
IoctlZcullGetCtxSize params{};
std::memcpy(&params, input.data(), input.size());
params.size = 0x1;
@@ -105,7 +114,7 @@ u32 nvhost_ctrl_gpu::ZCullGetCtxSize(const std::vector<u8>& input, std::vector<u
}
u32 nvhost_ctrl_gpu::ZCullGetInfo(const std::vector<u8>& input, std::vector<u8>& output) {
- LOG_DEBUG(Service_NVDRV, "called");
+ NGLOG_DEBUG(Service_NVDRV, "called");
IoctlNvgpuGpuZcullGetInfoArgs params{};
std::memcpy(&params, input.data(), input.size());
params.width_align_pixels = 0x20;
@@ -122,6 +131,31 @@ u32 nvhost_ctrl_gpu::ZCullGetInfo(const std::vector<u8>& input, std::vector<u8>&
return 0;
}
-} // namespace Devices
-} // namespace Nvidia
-} // namespace Service
+u32 nvhost_ctrl_gpu::ZBCSetTable(const std::vector<u8>& input, std::vector<u8>& output) {
+ NGLOG_WARNING(Service_NVDRV, "(STUBBED) called");
+ IoctlZbcSetTable params{};
+ std::memcpy(&params, input.data(), input.size());
+ // TODO(ogniK): What does this even actually do?
+ std::memcpy(output.data(), &params, output.size());
+ return 0;
+}
+
+u32 nvhost_ctrl_gpu::ZBCQueryTable(const std::vector<u8>& input, std::vector<u8>& output) {
+ NGLOG_WARNING(Service_NVDRV, "(STUBBED) called");
+ IoctlZbcQueryTable params{};
+ std::memcpy(&params, input.data(), input.size());
+ // TODO : To implement properly
+ std::memcpy(output.data(), &params, output.size());
+ return 0;
+}
+
+u32 nvhost_ctrl_gpu::FlushL2(const std::vector<u8>& input, std::vector<u8>& output) {
+ NGLOG_WARNING(Service_NVDRV, "(STUBBED) called");
+ IoctlFlushL2 params{};
+ std::memcpy(&params, input.data(), input.size());
+ // TODO : To implement properly
+ std::memcpy(output.data(), &params, output.size());
+ return 0;
+}
+
+} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h
index dc0476993..f09113e67 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h
@@ -9,9 +9,7 @@
#include "common/swap.h"
#include "core/hle/service/nvdrv/devices/nvdevice.h"
-namespace Service {
-namespace Nvidia {
-namespace Devices {
+namespace Service::Nvidia::Devices {
class nvhost_ctrl_gpu final : public nvdevice {
public:
@@ -27,6 +25,19 @@ private:
IocGetActiveSlotMaskCommand = 0x80084714,
IocZcullGetCtxSizeCommand = 0x80044701,
IocZcullGetInfo = 0x80284702,
+ IocZbcSetTable = 0x402C4703,
+ IocZbcQueryTable = 0xC0344704,
+ IocFlushL2 = 0x40084707,
+ IocInvalICache = 0x4008470D,
+ IocSetMmudebugMode = 0x4008470E,
+ IocSetSmDebugMode = 0x4010470F,
+ IocWaitForPause = 0xC0084710,
+ IocGetTcpExceptionEnStatus = 0x80084711,
+ IocNumVsms = 0x80084712,
+ IocVsmsMapping = 0xC0044713,
+ IocGetErrorChannelUserData = 0xC008471B,
+ IocGetGpuTime = 0xC010471C,
+ IocGetCpuTimeCorrelationInfo = 0xC108471D,
};
struct IoctlGpuCharacteristics {
@@ -88,7 +99,7 @@ private:
/// [in] pointer to TPC mask buffer. It will receive one 32-bit TPC mask per GPC or 0 if
/// GPC is not enabled or not present. This parameter is ignored if mask_buf_size is 0.
u64_le mask_buf_addr;
- u64_le unk; // Nintendo add this?
+ u64_le tpc_mask_size; // Nintendo add this?
};
static_assert(sizeof(IoctlGpuGetTpcMasksArgs) == 24,
"IoctlGpuGetTpcMasksArgs is incorrect size");
@@ -119,12 +130,40 @@ private:
static_assert(sizeof(IoctlNvgpuGpuZcullGetInfoArgs) == 40,
"IoctlNvgpuGpuZcullGetInfoArgs is incorrect size");
+ struct IoctlZbcSetTable {
+ u32_le color_ds[4];
+ u32_le color_l2[4];
+ u32_le depth;
+ u32_le format;
+ u32_le type;
+ };
+ static_assert(sizeof(IoctlZbcSetTable) == 44, "IoctlZbcSetTable is incorrect size");
+
+ struct IoctlZbcQueryTable {
+ u32_le color_ds[4];
+ u32_le color_l2[4];
+ u32_le depth;
+ u32_le ref_cnt;
+ u32_le format;
+ u32_le type;
+ u32_le index_size;
+ };
+ static_assert(sizeof(IoctlZbcQueryTable) == 52, "IoctlZbcQueryTable is incorrect size");
+
+ struct IoctlFlushL2 {
+ u32_le flush; // l2_flush | l2_invalidate << 1 | fb_flush << 2
+ u32_le reserved;
+ };
+ static_assert(sizeof(IoctlFlushL2) == 8, "IoctlFlushL2 is incorrect size");
+
u32 GetCharacteristics(const std::vector<u8>& input, std::vector<u8>& output);
u32 GetTPCMasks(const std::vector<u8>& input, std::vector<u8>& output);
u32 GetActiveSlotMask(const std::vector<u8>& input, std::vector<u8>& output);
u32 ZCullGetCtxSize(const std::vector<u8>& input, std::vector<u8>& output);
u32 ZCullGetInfo(const std::vector<u8>& input, std::vector<u8>& output);
+ u32 ZBCSetTable(const std::vector<u8>& input, std::vector<u8>& output);
+ u32 ZBCQueryTable(const std::vector<u8>& input, std::vector<u8>& output);
+ u32 FlushL2(const std::vector<u8>& input, std::vector<u8>& output);
};
-} // namespace Devices
-} // namespace Nvidia
-} // namespace Service
+
+} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
index da44c65f3..79aab87f9 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
@@ -9,13 +9,11 @@
#include "core/core.h"
#include "core/hle/service/nvdrv/devices/nvhost_gpu.h"
-namespace Service {
-namespace Nvidia {
-namespace Devices {
+namespace Service::Nvidia::Devices {
u32 nvhost_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) {
- LOG_DEBUG(Service_NVDRV, "called, command=0x%08x, input_size=0x%zx, output_size=0x%zx",
- command.raw, input.size(), output.size());
+ NGLOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}",
+ command.raw, input.size(), output.size());
switch (static_cast<IoctlCommand>(command.raw)) {
case IoctlCommand::IocSetNVMAPfdCommand:
@@ -34,6 +32,10 @@ u32 nvhost_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u
return AllocGPFIFOEx2(input, output);
case IoctlCommand::IocAllocObjCtxCommand:
return AllocateObjectContext(input, output);
+ case IoctlCommand::IocChannelGetWaitbaseCommand:
+ return GetWaitbase(input, output);
+ case IoctlCommand::IocChannelSetTimeoutCommand:
+ return ChannelSetTimeout(input, output);
}
if (command.group == NVGPU_IOCTL_MAGIC) {
@@ -42,30 +44,28 @@ u32 nvhost_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u
}
}
- UNIMPLEMENTED();
+ UNIMPLEMENTED_MSG("Unimplemented ioctl");
return 0;
};
u32 nvhost_gpu::SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output) {
IoctlSetNvmapFD params{};
std::memcpy(&params, input.data(), input.size());
- LOG_DEBUG(Service_NVDRV, "called, fd=%x", params.nvmap_fd);
+ NGLOG_DEBUG(Service_NVDRV, "called, fd={}", params.nvmap_fd);
nvmap_fd = params.nvmap_fd;
- std::memcpy(output.data(), &params, output.size());
return 0;
}
u32 nvhost_gpu::SetClientData(const std::vector<u8>& input, std::vector<u8>& output) {
- LOG_DEBUG(Service_NVDRV, "called");
+ NGLOG_DEBUG(Service_NVDRV, "called");
IoctlClientData params{};
std::memcpy(&params, input.data(), input.size());
user_data = params.data;
- std::memcpy(output.data(), &params, output.size());
return 0;
}
u32 nvhost_gpu::GetClientData(const std::vector<u8>& input, std::vector<u8>& output) {
- LOG_DEBUG(Service_NVDRV, "called");
+ NGLOG_DEBUG(Service_NVDRV, "called");
IoctlClientData params{};
std::memcpy(&params, input.data(), input.size());
params.data = user_data;
@@ -75,8 +75,8 @@ u32 nvhost_gpu::GetClientData(const std::vector<u8>& input, std::vector<u8>& out
u32 nvhost_gpu::ZCullBind(const std::vector<u8>& input, std::vector<u8>& output) {
std::memcpy(&zcull_params, input.data(), input.size());
- LOG_DEBUG(Service_NVDRV, "called, gpu_va=%" PRIx64 ", mode=%x", zcull_params.gpu_va,
- zcull_params.mode);
+ NGLOG_DEBUG(Service_NVDRV, "called, gpu_va={:X}, mode={:X}", zcull_params.gpu_va,
+ zcull_params.mode);
std::memcpy(output.data(), &zcull_params, output.size());
return 0;
}
@@ -84,26 +84,26 @@ u32 nvhost_gpu::ZCullBind(const std::vector<u8>& input, std::vector<u8>& output)
u32 nvhost_gpu::SetErrorNotifier(const std::vector<u8>& input, std::vector<u8>& output) {
IoctlSetErrorNotifier params{};
std::memcpy(&params, input.data(), input.size());
- LOG_WARNING(Service_NVDRV, "(STUBBED) called, offset=%" PRIx64 ", size=%" PRIx64 ", mem=%x",
- params.offset, params.size, params.mem);
+ NGLOG_WARNING(Service_NVDRV, "(STUBBED) called, offset={:X}, size={:X}, mem={:X}",
+ params.offset, params.size, params.mem);
std::memcpy(output.data(), &params, output.size());
return 0;
}
u32 nvhost_gpu::SetChannelPriority(const std::vector<u8>& input, std::vector<u8>& output) {
std::memcpy(&channel_priority, input.data(), input.size());
- LOG_DEBUG(Service_NVDRV, "(STUBBED) called, priority=%x", channel_priority);
- std::memcpy(output.data(), &channel_priority, output.size());
+ NGLOG_DEBUG(Service_NVDRV, "(STUBBED) called, priority={:X}", channel_priority);
return 0;
}
u32 nvhost_gpu::AllocGPFIFOEx2(const std::vector<u8>& input, std::vector<u8>& output) {
IoctlAllocGpfifoEx2 params{};
std::memcpy(&params, input.data(), input.size());
- LOG_WARNING(Service_NVDRV,
- "(STUBBED) called, num_entries=%x, flags=%x, unk0=%x, unk1=%x, unk2=%x, unk3=%x",
- params.num_entries, params.flags, params.unk0, params.unk1, params.unk2,
- params.unk3);
+ NGLOG_WARNING(Service_NVDRV,
+ "(STUBBED) called, num_entries={:X}, flags={:X}, unk0={:X}, "
+ "unk1={:X}, unk2={:X}, unk3={:X}",
+ params.num_entries, params.flags, params.unk0, params.unk1, params.unk2,
+ params.unk3);
params.fence_out.id = 0;
params.fence_out.value = 0;
std::memcpy(output.data(), &params, output.size());
@@ -113,8 +113,8 @@ u32 nvhost_gpu::AllocGPFIFOEx2(const std::vector<u8>& input, std::vector<u8>& ou
u32 nvhost_gpu::AllocateObjectContext(const std::vector<u8>& input, std::vector<u8>& output) {
IoctlAllocObjCtx params{};
std::memcpy(&params, input.data(), input.size());
- LOG_WARNING(Service_NVDRV, "(STUBBED) called, class_num=%x, flags=%x", params.class_num,
- params.flags);
+ NGLOG_WARNING(Service_NVDRV, "(STUBBED) called, class_num={:X}, flags={:X}", params.class_num,
+ params.flags);
params.obj_id = 0x0;
std::memcpy(output.data(), &params, output.size());
return 0;
@@ -125,8 +125,8 @@ u32 nvhost_gpu::SubmitGPFIFO(const std::vector<u8>& input, std::vector<u8>& outp
UNIMPLEMENTED();
IoctlSubmitGpfifo params{};
std::memcpy(&params, input.data(), sizeof(IoctlSubmitGpfifo));
- LOG_WARNING(Service_NVDRV, "(STUBBED) called, gpfifo=%" PRIx64 ", num_entries=%x, flags=%x",
- params.gpfifo, params.num_entries, params.flags);
+ NGLOG_WARNING(Service_NVDRV, "(STUBBED) called, gpfifo={:X}, num_entries={:X}, flags={:X}",
+ params.gpfifo, params.num_entries, params.flags);
auto entries = std::vector<IoctlGpfifoEntry>();
entries.resize(params.num_entries);
@@ -142,6 +142,20 @@ u32 nvhost_gpu::SubmitGPFIFO(const std::vector<u8>& input, std::vector<u8>& outp
return 0;
}
-} // namespace Devices
-} // namespace Nvidia
-} // namespace Service
+u32 nvhost_gpu::GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output) {
+ IoctlGetWaitbase params{};
+ std::memcpy(&params, input.data(), sizeof(IoctlGetWaitbase));
+ NGLOG_INFO(Service_NVDRV, "called, unknown=0x{:X}", params.unknown);
+ params.value = 0; // Seems to be hard coded at 0
+ std::memcpy(output.data(), &params, output.size());
+ return 0;
+}
+
+u32 nvhost_gpu::ChannelSetTimeout(const std::vector<u8>& input, std::vector<u8>& output) {
+ IoctlChannelSetTimeout params{};
+ std::memcpy(&params, input.data(), sizeof(IoctlChannelSetTimeout));
+ NGLOG_INFO(Service_NVDRV, "called, timeout=0x{:X}", params.timeout);
+ return 0;
+}
+
+} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h
index e7e9a0088..56b5ed60d 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h
@@ -10,9 +10,7 @@
#include "common/swap.h"
#include "core/hle/service/nvdrv/devices/nvdevice.h"
-namespace Service {
-namespace Nvidia {
-namespace Devices {
+namespace Service::Nvidia::Devices {
class nvmap;
constexpr u32 NVGPU_IOCTL_MAGIC('H');
@@ -28,13 +26,23 @@ public:
private:
enum class IoctlCommand : u32_le {
IocSetNVMAPfdCommand = 0x40044801,
+ IocAllocGPFIFOCommand = 0x40084805,
IocSetClientDataCommand = 0x40084714,
IocGetClientDataCommand = 0x80084715,
IocZCullBind = 0xc010480b,
IocSetErrorNotifierCommand = 0xC018480C,
IocChannelSetPriorityCommand = 0x4004480D,
+ IocEnableCommand = 0x0000480E,
+ IocDisableCommand = 0x0000480F,
+ IocPreemptCommand = 0x00004810,
+ IocForceResetCommand = 0x00004811,
+ IocEventIdControlCommand = 0x40084812,
+ IocGetErrorNotificationCommand = 0xC0104817,
+ IocAllocGPFIFOExCommand = 0x40204818,
IocAllocGPFIFOEx2Command = 0xC020481A,
IocAllocObjCtxCommand = 0xC0104809,
+ IocChannelGetWaitbaseCommand = 0xC0080003,
+ IocChannelSetTimeoutCommand = 0x40044803,
};
enum class CtxObjects : u32_le {
@@ -51,6 +59,17 @@ private:
};
static_assert(sizeof(IoctlSetNvmapFD) == 4, "IoctlSetNvmapFD is incorrect size");
+ struct IoctlChannelSetTimeout {
+ u32_le timeout;
+ };
+ static_assert(sizeof(IoctlChannelSetTimeout) == 4, "IoctlChannelSetTimeout is incorrect size");
+
+ struct IoctlAllocGPFIFO {
+ u32_le num_entries;
+ u32_le flags;
+ };
+ static_assert(sizeof(IoctlAllocGPFIFO) == 8, "IoctlAllocGPFIFO is incorrect size");
+
struct IoctlClientData {
u64_le data;
};
@@ -71,12 +90,45 @@ private:
};
static_assert(sizeof(IoctlSetErrorNotifier) == 24, "IoctlSetErrorNotifier is incorrect size");
+ struct IoctlChannelSetPriority {
+ u32_le priority;
+ };
+ static_assert(sizeof(IoctlChannelSetPriority) == 4,
+ "IoctlChannelSetPriority is incorrect size");
+
+ struct IoctlEventIdControl {
+ u32_le cmd; // 0=disable, 1=enable, 2=clear
+ u32_le id;
+ };
+ static_assert(sizeof(IoctlEventIdControl) == 8, "IoctlEventIdControl is incorrect size");
+
+ struct IoctlGetErrorNotification {
+ u64_le timestamp;
+ u32_le info32;
+ u16_le info16;
+ u16_le status; // always 0xFFFF
+ };
+ static_assert(sizeof(IoctlGetErrorNotification) == 16,
+ "IoctlGetErrorNotification is incorrect size");
+
struct IoctlFence {
u32_le id;
u32_le value;
};
static_assert(sizeof(IoctlFence) == 8, "IoctlFence is incorrect size");
+ struct IoctlAllocGpfifoEx {
+ u32_le num_entries;
+ u32_le flags;
+ u32_le unk0;
+ u32_le unk1;
+ u32_le unk2;
+ u32_le unk3;
+ u32_le unk4;
+ u32_le unk5;
+ };
+ static_assert(sizeof(IoctlAllocGpfifoEx) == 32, "IoctlAllocGpfifoEx is incorrect size");
+
struct IoctlAllocGpfifoEx2 {
u32_le num_entries; // in
u32_le flags; // in
@@ -119,7 +171,13 @@ private:
IoctlFence fence_out; // returned new fence object for others to wait on
};
static_assert(sizeof(IoctlSubmitGpfifo) == 16 + sizeof(IoctlFence),
- "submit_gpfifo is incorrect size");
+ "IoctlSubmitGpfifo is incorrect size");
+
+ struct IoctlGetWaitbase {
+ u32 unknown; // seems to be ignored? Nintendo added this
+ u32 value;
+ };
+ static_assert(sizeof(IoctlGetWaitbase) == 8, "IoctlGetWaitbase is incorrect size");
u32_le nvmap_fd{};
u64_le user_data{};
@@ -135,10 +193,10 @@ private:
u32 AllocGPFIFOEx2(const std::vector<u8>& input, std::vector<u8>& output);
u32 AllocateObjectContext(const std::vector<u8>& input, std::vector<u8>& output);
u32 SubmitGPFIFO(const std::vector<u8>& input, std::vector<u8>& output);
+ u32 GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output);
+ u32 ChannelSetTimeout(const std::vector<u8>& input, std::vector<u8>& output);
std::shared_ptr<nvmap> nvmap_dev;
};
-} // namespace Devices
-} // namespace Nvidia
-} // namespace Service
+} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp
new file mode 100644
index 000000000..0b6c22898
--- /dev/null
+++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp
@@ -0,0 +1,32 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/assert.h"
+#include "common/logging/log.h"
+#include "core/hle/service/nvdrv/devices/nvhost_nvdec.h"
+
+namespace Service::Nvidia::Devices {
+
+u32 nvhost_nvdec::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) {
+ NGLOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}",
+ command.raw, input.size(), output.size());
+
+ switch (static_cast<IoctlCommand>(command.raw)) {
+ case IoctlCommand::IocSetNVMAPfdCommand:
+ return SetNVMAPfd(input, output);
+ }
+
+ UNIMPLEMENTED_MSG("Unimplemented ioctl");
+ return 0;
+}
+
+u32 nvhost_nvdec::SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output) {
+ IoctlSetNvmapFD params{};
+ std::memcpy(&params, input.data(), input.size());
+ NGLOG_DEBUG(Service_NVDRV, "called, fd={}", params.nvmap_fd);
+ nvmap_fd = params.nvmap_fd;
+ return 0;
+}
+
+} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h
new file mode 100644
index 000000000..0192aecdd
--- /dev/null
+++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h
@@ -0,0 +1,38 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <cstdlib>
+#include <cstring>
+#include <vector>
+#include "common/common_types.h"
+#include "core/hle/service/nvdrv/devices/nvdevice.h"
+
+namespace Service::Nvidia::Devices {
+
+class nvhost_nvdec final : public nvdevice {
+public:
+ nvhost_nvdec() = default;
+ ~nvhost_nvdec() override = default;
+
+ u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override;
+
+private:
+ enum class IoctlCommand : u32_le {
+ IocSetNVMAPfdCommand = 0x40044801,
+ };
+
+ struct IoctlSetNvmapFD {
+ u32_le nvmap_fd;
+ };
+ static_assert(sizeof(IoctlSetNvmapFD) == 4, "IoctlSetNvmapFD is incorrect size");
+
+ u32_le nvmap_fd{};
+
+ u32 SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output);
+};
+
+} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvmap.cpp b/src/core/hle/service/nvdrv/devices/nvmap.cpp
index b3842eb4c..23fe98190 100644
--- a/src/core/hle/service/nvdrv/devices/nvmap.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvmap.cpp
@@ -9,9 +9,7 @@
#include "common/logging/log.h"
#include "core/hle/service/nvdrv/devices/nvmap.h"
-namespace Service {
-namespace Nvidia {
-namespace Devices {
+namespace Service::Nvidia::Devices {
VAddr nvmap::GetObjectAddress(u32 handle) const {
auto object = GetObject(handle);
@@ -32,9 +30,11 @@ u32 nvmap::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& o
return IocFromId(input, output);
case IoctlCommand::Param:
return IocParam(input, output);
+ case IoctlCommand::Free:
+ return IocFree(input, output);
}
- UNIMPLEMENTED();
+ UNIMPLEMENTED_MSG("Unimplemented ioctl");
return 0;
}
@@ -47,11 +47,12 @@ u32 nvmap::IocCreate(const std::vector<u8>& input, std::vector<u8>& output) {
object->id = next_id++;
object->size = params.size;
object->status = Object::Status::Created;
+ object->refcount = 1;
u32 handle = next_handle++;
handles[handle] = std::move(object);
- LOG_DEBUG(Service_NVDRV, "size=0x%08X", params.size);
+ NGLOG_DEBUG(Service_NVDRV, "size=0x{:08X}", params.size);
params.handle = handle;
@@ -72,7 +73,7 @@ u32 nvmap::IocAlloc(const std::vector<u8>& input, std::vector<u8>& output) {
object->addr = params.addr;
object->status = Object::Status::Allocated;
- LOG_DEBUG(Service_NVDRV, "called, addr=0x%" PRIx64, params.addr);
+ NGLOG_DEBUG(Service_NVDRV, "called, addr={:X}", params.addr);
std::memcpy(output.data(), &params, sizeof(params));
return 0;
@@ -82,7 +83,7 @@ u32 nvmap::IocGetId(const std::vector<u8>& input, std::vector<u8>& output) {
IocGetIdParams params;
std::memcpy(&params, input.data(), sizeof(params));
- LOG_WARNING(Service_NVDRV, "called");
+ NGLOG_WARNING(Service_NVDRV, "called");
auto object = GetObject(params.handle);
ASSERT(object);
@@ -97,12 +98,14 @@ u32 nvmap::IocFromId(const std::vector<u8>& input, std::vector<u8>& output) {
IocFromIdParams params;
std::memcpy(&params, input.data(), sizeof(params));
- LOG_WARNING(Service_NVDRV, "(STUBBED) called");
+ NGLOG_WARNING(Service_NVDRV, "(STUBBED) called");
auto itr = std::find_if(handles.begin(), handles.end(),
[&](const auto& entry) { return entry.second->id == params.id; });
ASSERT(itr != handles.end());
+ itr->second->refcount++;
+
// Return the existing handle instead of creating a new one.
params.handle = itr->first;
@@ -116,25 +119,25 @@ u32 nvmap::IocParam(const std::vector<u8>& input, std::vector<u8>& output) {
IocParamParams params;
std::memcpy(&params, input.data(), sizeof(params));
- LOG_WARNING(Service_NVDRV, "(STUBBED) called type=%u", params.type);
+ NGLOG_WARNING(Service_NVDRV, "(STUBBED) called type={}", params.param);
auto object = GetObject(params.handle);
ASSERT(object);
ASSERT(object->status == Object::Status::Allocated);
- switch (static_cast<ParamTypes>(params.type)) {
+ switch (static_cast<ParamTypes>(params.param)) {
case ParamTypes::Size:
- params.value = object->size;
+ params.result = object->size;
break;
case ParamTypes::Alignment:
- params.value = object->align;
+ params.result = object->align;
break;
case ParamTypes::Heap:
// TODO(Subv): Seems to be a hardcoded value?
- params.value = 0x40000000;
+ params.result = 0x40000000;
break;
case ParamTypes::Kind:
- params.value = object->kind;
+ params.result = object->kind;
break;
default:
UNIMPLEMENTED();
@@ -144,6 +147,34 @@ u32 nvmap::IocParam(const std::vector<u8>& input, std::vector<u8>& output) {
return 0;
}
-} // namespace Devices
-} // namespace Nvidia
-} // namespace Service
+u32 nvmap::IocFree(const std::vector<u8>& input, std::vector<u8>& output) {
+ enum FreeFlags {
+ Freed = 0,
+ NotFreedYet = 1,
+ };
+
+ IocFreeParams params;
+ std::memcpy(&params, input.data(), sizeof(params));
+
+ NGLOG_WARNING(Service_NVDRV, "(STUBBED) called");
+
+ auto itr = handles.find(params.handle);
+ ASSERT(itr != handles.end());
+
+ itr->second->refcount--;
+
+ params.refcount = itr->second->refcount;
+ params.size = itr->second->size;
+
+ if (itr->second->refcount == 0)
+ params.flags = Freed;
+ else
+ params.flags = NotFreedYet;
+
+ handles.erase(params.handle);
+
+ std::memcpy(output.data(), &params, sizeof(params));
+ return 0;
+}
+
+} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvmap.h b/src/core/hle/service/nvdrv/devices/nvmap.h
index 4681e438b..39fafaa7c 100644
--- a/src/core/hle/service/nvdrv/devices/nvmap.h
+++ b/src/core/hle/service/nvdrv/devices/nvmap.h
@@ -12,9 +12,7 @@
#include "common/swap.h"
#include "core/hle/service/nvdrv/devices/nvdevice.h"
-namespace Service {
-namespace Nvidia {
-namespace Devices {
+namespace Service::Nvidia::Devices {
class nvmap final : public nvdevice {
public:
@@ -36,6 +34,7 @@ public:
u8 kind;
VAddr addr;
Status status;
+ u32 refcount;
};
std::shared_ptr<Object> GetObject(u32 handle) const {
@@ -60,16 +59,25 @@ private:
Create = 0xC0080101,
FromId = 0xC0080103,
Alloc = 0xC0200104,
+ Free = 0xC0180105,
Param = 0xC00C0109,
- GetId = 0xC008010E
+ GetId = 0xC008010E,
};
-
struct IocCreateParams {
// Input
u32_le size;
// Output
u32_le handle;
};
+ static_assert(sizeof(IocCreateParams) == 8, "IocCreateParams has wrong size");
+
+ struct IocFromIdParams {
+ // Input
+ u32_le id;
+ // Output
+ u32_le handle;
+ };
+ static_assert(sizeof(IocFromIdParams) == 8, "IocFromIdParams has wrong size");
struct IocAllocParams {
// Input
@@ -81,36 +89,40 @@ private:
INSERT_PADDING_BYTES(7);
u64_le addr;
};
+ static_assert(sizeof(IocAllocParams) == 32, "IocAllocParams has wrong size");
- struct IocGetIdParams {
- // Output
- u32_le id;
- // Input
+ struct IocFreeParams {
u32_le handle;
+ INSERT_PADDING_BYTES(4);
+ u64_le refcount;
+ u32_le size;
+ u32_le flags;
};
+ static_assert(sizeof(IocFreeParams) == 24, "IocFreeParams has wrong size");
- struct IocFromIdParams {
+ struct IocParamParams {
// Input
- u32_le id;
- // Output
u32_le handle;
+ u32_le param;
+ // Output
+ u32_le result;
};
+ static_assert(sizeof(IocParamParams) == 12, "IocParamParams has wrong size");
- struct IocParamParams {
+ struct IocGetIdParams {
+ // Output
+ u32_le id;
// Input
u32_le handle;
- u32_le type;
- // Output
- u32_le value;
};
+ static_assert(sizeof(IocGetIdParams) == 8, "IocGetIdParams has wrong size");
u32 IocCreate(const std::vector<u8>& input, std::vector<u8>& output);
u32 IocAlloc(const std::vector<u8>& input, std::vector<u8>& output);
u32 IocGetId(const std::vector<u8>& input, std::vector<u8>& output);
u32 IocFromId(const std::vector<u8>& input, std::vector<u8>& output);
u32 IocParam(const std::vector<u8>& input, std::vector<u8>& output);
+ u32 IocFree(const std::vector<u8>& input, std::vector<u8>& output);
};
-} // namespace Devices
-} // namespace Nvidia
-} // namespace Service
+} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/interface.cpp b/src/core/hle/service/nvdrv/interface.cpp
index c70370f1f..45d2862ef 100644
--- a/src/core/hle/service/nvdrv/interface.cpp
+++ b/src/core/hle/service/nvdrv/interface.cpp
@@ -9,11 +9,10 @@
#include "core/hle/service/nvdrv/interface.h"
#include "core/hle/service/nvdrv/nvdrv.h"
-namespace Service {
-namespace Nvidia {
+namespace Service::Nvidia {
void NVDRV::Open(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_NVDRV, "called");
+ NGLOG_DEBUG(Service_NVDRV, "called");
const auto& buffer = ctx.ReadBuffer();
std::string device_name(buffer.begin(), buffer.end());
@@ -26,7 +25,7 @@ void NVDRV::Open(Kernel::HLERequestContext& ctx) {
}
void NVDRV::Ioctl(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_NVDRV, "called");
+ NGLOG_DEBUG(Service_NVDRV, "called");
IPC::RequestParser rp{ctx};
u32 fd = rp.Pop<u32>();
@@ -42,7 +41,7 @@ void NVDRV::Ioctl(Kernel::HLERequestContext& ctx) {
}
void NVDRV::Close(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_NVDRV, "called");
+ NGLOG_DEBUG(Service_NVDRV, "called");
IPC::RequestParser rp{ctx};
u32 fd = rp.Pop<u32>();
@@ -54,7 +53,7 @@ void NVDRV::Close(Kernel::HLERequestContext& ctx) {
}
void NVDRV::Initialize(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_NVDRV, "(STUBBED) called");
+ NGLOG_WARNING(Service_NVDRV, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(0);
@@ -64,7 +63,7 @@ void NVDRV::QueryEvent(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
u32 fd = rp.Pop<u32>();
u32 event_id = rp.Pop<u32>();
- LOG_WARNING(Service_NVDRV, "(STUBBED) called, fd=%x, event_id=%x", fd, event_id);
+ NGLOG_WARNING(Service_NVDRV, "(STUBBED) called, fd={:X}, event_id={:X}", fd, event_id);
IPC::ResponseBuilder rb{ctx, 3, 1};
rb.Push(RESULT_SUCCESS);
@@ -76,14 +75,14 @@ void NVDRV::SetClientPID(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
pid = rp.Pop<u64>();
- LOG_WARNING(Service_NVDRV, "(STUBBED) called, pid=0x%" PRIx64, pid);
+ NGLOG_WARNING(Service_NVDRV, "(STUBBED) called, pid=0x{:X}", pid);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(0);
}
void NVDRV::FinishInitialize(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_NVDRV, "(STUBBED) called");
+ NGLOG_WARNING(Service_NVDRV, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
@@ -96,7 +95,14 @@ NVDRV::NVDRV(std::shared_ptr<Module> nvdrv, const char* name)
{2, &NVDRV::Close, "Close"},
{3, &NVDRV::Initialize, "Initialize"},
{4, &NVDRV::QueryEvent, "QueryEvent"},
+ {5, nullptr, "MapSharedMem"},
+ {6, nullptr, "GetStatus"},
+ {7, nullptr, "ForceSetClientPID"},
{8, &NVDRV::SetClientPID, "SetClientPID"},
+ {9, nullptr, "DumpGraphicsMemoryInfo"},
+ {10, nullptr, "InitializeDevtools"},
+ {11, nullptr, "Ioctl2"},
+ {12, nullptr, "Ioctl3"},
{13, &NVDRV::FinishInitialize, "FinishInitialize"},
};
RegisterHandlers(functions);
@@ -104,5 +110,4 @@ NVDRV::NVDRV(std::shared_ptr<Module> nvdrv, const char* name)
query_event = Kernel::Event::Create(Kernel::ResetType::OneShot, "NVDRV::query_event");
}
-} // namespace Nvidia
-} // namespace Service
+} // namespace Service::Nvidia
diff --git a/src/core/hle/service/nvdrv/interface.h b/src/core/hle/service/nvdrv/interface.h
index daf2302af..959b5ba29 100644
--- a/src/core/hle/service/nvdrv/interface.h
+++ b/src/core/hle/service/nvdrv/interface.h
@@ -10,8 +10,7 @@
#include "core/hle/service/nvdrv/nvdrv.h"
#include "core/hle/service/service.h"
-namespace Service {
-namespace Nvidia {
+namespace Service::Nvidia {
class NVDRV final : public ServiceFramework<NVDRV> {
public:
@@ -34,5 +33,4 @@ private:
Kernel::SharedPtr<Kernel::Event> query_event;
};
-} // namespace Nvidia
-} // namespace Service
+} // namespace Service::Nvidia
diff --git a/src/core/hle/service/nvdrv/nvdrv.cpp b/src/core/hle/service/nvdrv/nvdrv.cpp
index ea00240e6..cc5cfe34e 100644
--- a/src/core/hle/service/nvdrv/nvdrv.cpp
+++ b/src/core/hle/service/nvdrv/nvdrv.cpp
@@ -9,13 +9,13 @@
#include "core/hle/service/nvdrv/devices/nvhost_ctrl.h"
#include "core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h"
#include "core/hle/service/nvdrv/devices/nvhost_gpu.h"
+#include "core/hle/service/nvdrv/devices/nvhost_nvdec.h"
#include "core/hle/service/nvdrv/devices/nvmap.h"
#include "core/hle/service/nvdrv/interface.h"
#include "core/hle/service/nvdrv/nvdrv.h"
#include "core/hle/service/nvdrv/nvmemp.h"
-namespace Service {
-namespace Nvidia {
+namespace Service::Nvidia {
std::weak_ptr<Module> nvdrv;
@@ -37,11 +37,12 @@ Module::Module() {
devices["/dev/nvmap"] = nvmap_dev;
devices["/dev/nvdisp_disp0"] = std::make_shared<Devices::nvdisp_disp0>(nvmap_dev);
devices["/dev/nvhost-ctrl"] = std::make_shared<Devices::nvhost_ctrl>();
+ devices["/dev/nvhost-nvdec"] = std::make_shared<Devices::nvhost_nvdec>();
}
u32 Module::Open(std::string device_name) {
- ASSERT_MSG(devices.find(device_name) != devices.end(), "Trying to open unknown device %s",
- device_name.c_str());
+ ASSERT_MSG(devices.find(device_name) != devices.end(), "Trying to open unknown device {}",
+ device_name);
auto device = devices[device_name];
u32 fd = next_fd++;
@@ -69,5 +70,4 @@ ResultCode Module::Close(u32 fd) {
return RESULT_SUCCESS;
}
-} // namespace Nvidia
-} // namespace Service
+} // namespace Service::Nvidia
diff --git a/src/core/hle/service/nvdrv/nvdrv.h b/src/core/hle/service/nvdrv/nvdrv.h
index 6a55ff96d..579940817 100644
--- a/src/core/hle/service/nvdrv/nvdrv.h
+++ b/src/core/hle/service/nvdrv/nvdrv.h
@@ -10,8 +10,7 @@
#include "common/common_types.h"
#include "core/hle/service/service.h"
-namespace Service {
-namespace Nvidia {
+namespace Service::Nvidia {
namespace Devices {
class nvdevice;
@@ -61,5 +60,4 @@ void InstallInterfaces(SM::ServiceManager& service_manager);
extern std::weak_ptr<Module> nvdrv;
-} // namespace Nvidia
-} // namespace Service
+} // namespace Service::Nvidia
diff --git a/src/core/hle/service/nvdrv/nvmemp.cpp b/src/core/hle/service/nvdrv/nvmemp.cpp
index 5a13732c7..9ca6e5512 100644
--- a/src/core/hle/service/nvdrv/nvmemp.cpp
+++ b/src/core/hle/service/nvdrv/nvmemp.cpp
@@ -8,24 +8,22 @@
#include "core/hle/service/nvdrv/nvdrv.h"
#include "core/hle/service/nvdrv/nvmemp.h"
-namespace Service {
-namespace Nvidia {
+namespace Service::Nvidia {
NVMEMP::NVMEMP() : ServiceFramework("nvmemp") {
static const FunctionInfo functions[] = {
- {0, &NVMEMP::Unknown0, "Unknown0"},
- {1, &NVMEMP::Unknown1, "Unknown1"},
+ {0, &NVMEMP::Cmd0, "Cmd0"},
+ {1, &NVMEMP::Cmd1, "Cmd1"},
};
RegisterHandlers(functions);
}
-void NVMEMP::Unknown0(Kernel::HLERequestContext& ctx) {
+void NVMEMP::Cmd0(Kernel::HLERequestContext& ctx) {
UNIMPLEMENTED();
}
-void NVMEMP::Unknown1(Kernel::HLERequestContext& ctx) {
+void NVMEMP::Cmd1(Kernel::HLERequestContext& ctx) {
UNIMPLEMENTED();
}
-} // namespace Nvidia
-} // namespace Service
+} // namespace Service::Nvidia
diff --git a/src/core/hle/service/nvdrv/nvmemp.h b/src/core/hle/service/nvdrv/nvmemp.h
index a6b5fbb82..dfdcabf4a 100644
--- a/src/core/hle/service/nvdrv/nvmemp.h
+++ b/src/core/hle/service/nvdrv/nvmemp.h
@@ -6,8 +6,7 @@
#include "core/hle/service/service.h"
-namespace Service {
-namespace Nvidia {
+namespace Service::Nvidia {
class NVMEMP final : public ServiceFramework<NVMEMP> {
public:
@@ -15,9 +14,8 @@ public:
~NVMEMP() = default;
private:
- void Unknown0(Kernel::HLERequestContext& ctx);
- void Unknown1(Kernel::HLERequestContext& ctx);
+ void Cmd0(Kernel::HLERequestContext& ctx);
+ void Cmd1(Kernel::HLERequestContext& ctx);
};
-} // namespace Nvidia
-} // namespace Service
+} // namespace Service::Nvidia
diff --git a/src/core/hle/service/nvflinger/buffer_queue.cpp b/src/core/hle/service/nvflinger/buffer_queue.cpp
index ff7b6b039..49e88b394 100644
--- a/src/core/hle/service/nvflinger/buffer_queue.cpp
+++ b/src/core/hle/service/nvflinger/buffer_queue.cpp
@@ -23,29 +23,33 @@ void BufferQueue::SetPreallocatedBuffer(u32 slot, IGBPBuffer& igbp_buffer) {
buffer.igbp_buffer = igbp_buffer;
buffer.status = Buffer::Status::Free;
- LOG_WARNING(Service, "Adding graphics buffer %u", slot);
+ NGLOG_WARNING(Service, "Adding graphics buffer {}", slot);
queue.emplace_back(buffer);
+
+ if (buffer_wait_event) {
+ buffer_wait_event->Signal();
+ }
}
-u32 BufferQueue::DequeueBuffer(u32 pixel_format, u32 width, u32 height) {
+boost::optional<u32> BufferQueue::DequeueBuffer(u32 width, u32 height) {
auto itr = std::find_if(queue.begin(), queue.end(), [&](const Buffer& buffer) {
// Only consider free buffers. Buffers become free once again after they've been Acquired
// and Released by the compositor, see the NVFlinger::Compose method.
- if (buffer.status != Buffer::Status::Free)
+ if (buffer.status != Buffer::Status::Free) {
return false;
+ }
// Make sure that the parameters match.
- auto& igbp_buffer = buffer.igbp_buffer;
- return igbp_buffer.format == pixel_format && igbp_buffer.width == width &&
- igbp_buffer.height == height;
+ return buffer.igbp_buffer.width == width && buffer.igbp_buffer.height == height;
});
+
if (itr == queue.end()) {
- LOG_CRITICAL(Service_NVDRV, "no free buffers for pixel_format=%d, width=%d, height=%d",
- pixel_format, width, height);
- itr = queue.begin();
+ return boost::none;
}
+ buffer_wait_event = nullptr;
+
itr->status = Buffer::Status::Dequeued;
return itr->slot;
}
@@ -83,10 +87,14 @@ void BufferQueue::ReleaseBuffer(u32 slot) {
ASSERT(itr != queue.end());
ASSERT(itr->status == Buffer::Status::Acquired);
itr->status = Buffer::Status::Free;
+
+ if (buffer_wait_event) {
+ buffer_wait_event->Signal();
+ }
}
u32 BufferQueue::Query(QueryType type) {
- LOG_WARNING(Service, "(STUBBED) called type=%u", static_cast<u32>(type));
+ NGLOG_WARNING(Service, "(STUBBED) called type={}", static_cast<u32>(type));
switch (type) {
case QueryType::NativeWindowFormat:
// TODO(Subv): Use an enum for this
@@ -98,5 +106,10 @@ u32 BufferQueue::Query(QueryType type) {
return 0;
}
+void BufferQueue::SetBufferWaitEvent(Kernel::SharedPtr<Kernel::Event>&& wait_event) {
+ ASSERT_MSG(!buffer_wait_event, "buffer_wait_event only supports a single waiting thread!");
+ buffer_wait_event = std::move(wait_event);
+}
+
} // namespace NVFlinger
} // namespace Service
diff --git a/src/core/hle/service/nvflinger/buffer_queue.h b/src/core/hle/service/nvflinger/buffer_queue.h
index ef9732769..1de5767cb 100644
--- a/src/core/hle/service/nvflinger/buffer_queue.h
+++ b/src/core/hle/service/nvflinger/buffer_queue.h
@@ -47,6 +47,8 @@ public:
~BufferQueue() = default;
enum class BufferTransformFlags : u32 {
+ /// No transform flags are set
+ Unset = 0x00,
/// Flip source image horizontally (around the vertical axis)
FlipH = 0x01,
/// Flip source image vertically (around the horizontal axis)
@@ -69,12 +71,13 @@ public:
};
void SetPreallocatedBuffer(u32 slot, IGBPBuffer& buffer);
- u32 DequeueBuffer(u32 pixel_format, u32 width, u32 height);
+ boost::optional<u32> DequeueBuffer(u32 width, u32 height);
const IGBPBuffer& RequestBuffer(u32 slot) const;
void QueueBuffer(u32 slot, BufferTransformFlags transform);
boost::optional<const Buffer&> AcquireBuffer();
void ReleaseBuffer(u32 slot);
u32 Query(QueryType type);
+ void SetBufferWaitEvent(Kernel::SharedPtr<Kernel::Event>&& wait_event);
u32 GetId() const {
return id;
@@ -90,6 +93,9 @@ private:
std::vector<Buffer> queue;
Kernel::SharedPtr<Kernel::Event> native_handle;
+
+ /// Used to signal waiting thread when no buffers are available
+ Kernel::SharedPtr<Kernel::Event> buffer_wait_event;
};
} // namespace NVFlinger
diff --git a/src/core/hle/service/nvflinger/nvflinger.cpp b/src/core/hle/service/nvflinger/nvflinger.cpp
index a54239b0f..5c50ed601 100644
--- a/src/core/hle/service/nvflinger/nvflinger.cpp
+++ b/src/core/hle/service/nvflinger/nvflinger.cpp
@@ -5,6 +5,7 @@
#include <algorithm>
#include "common/alignment.h"
+#include "common/microprofile.h"
#include "common/scope_exit.h"
#include "core/core.h"
#include "core/core_timing.h"
@@ -15,11 +16,10 @@
#include "video_core/renderer_base.h"
#include "video_core/video_core.h"
-namespace Service {
-namespace NVFlinger {
+namespace Service::NVFlinger {
constexpr size_t SCREEN_REFRESH_RATE = 60;
-constexpr u64 frame_ticks = static_cast<u64>(BASE_CLOCK_RATE / SCREEN_REFRESH_RATE);
+constexpr u64 frame_ticks = static_cast<u64>(CoreTiming::BASE_CLOCK_RATE / SCREEN_REFRESH_RATE);
NVFlinger::NVFlinger() {
// Add the different displays to the list of displays.
@@ -48,7 +48,7 @@ NVFlinger::~NVFlinger() {
}
u64 NVFlinger::OpenDisplay(const std::string& name) {
- LOG_WARNING(Service, "Opening display %s", name.c_str());
+ NGLOG_WARNING(Service, "Opening display {}", name);
// TODO(Subv): Currently we only support the Default display.
ASSERT(name == "Default");
@@ -128,6 +128,8 @@ void NVFlinger::Compose() {
// Search for a queued buffer and acquire it
auto buffer = buffer_queue->AcquireBuffer();
+ MicroProfileFlip();
+
if (buffer == boost::none) {
// There was no queued buffer to draw, render previous frame
Core::System::GetInstance().perf_stats.EndGameFrame();
@@ -150,6 +152,9 @@ void NVFlinger::Compose() {
igbp_buffer.width, igbp_buffer.height, igbp_buffer.stride, buffer->transform);
buffer_queue->ReleaseBuffer(buffer->slot);
+
+ // TODO(Subv): Figure out when we should actually signal this event.
+ buffer_queue->GetNativeHandle()->Signal();
}
}
@@ -159,5 +164,4 @@ Display::Display(u64 id, std::string name) : id(id), name(std::move(name)) {
vsync_event = Kernel::Event::Create(Kernel::ResetType::Pulse, "Display VSync Event");
}
-} // namespace NVFlinger
-} // namespace Service
+} // namespace Service::NVFlinger
diff --git a/src/core/hle/service/nvflinger/nvflinger.h b/src/core/hle/service/nvflinger/nvflinger.h
index 3126018ad..2c908297b 100644
--- a/src/core/hle/service/nvflinger/nvflinger.h
+++ b/src/core/hle/service/nvflinger/nvflinger.h
@@ -12,8 +12,7 @@ namespace CoreTiming {
struct EventType;
}
-namespace Service {
-namespace NVFlinger {
+namespace Service::NVFlinger {
class BufferQueue;
@@ -80,5 +79,4 @@ private:
CoreTiming::EventType* composition_event;
};
-} // namespace NVFlinger
-} // namespace Service
+} // namespace Service::NVFlinger
diff --git a/src/core/hle/service/pctl/module.cpp b/src/core/hle/service/pctl/module.cpp
new file mode 100644
index 000000000..dd20d5ae7
--- /dev/null
+++ b/src/core/hle/service/pctl/module.cpp
@@ -0,0 +1,146 @@
+// Copyright 2018 yuzu emulator team
+// 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/pctl/module.h"
+#include "core/hle/service/pctl/pctl.h"
+
+namespace Service::PCTL {
+
+class IParentalControlService final : public ServiceFramework<IParentalControlService> {
+public:
+ IParentalControlService() : ServiceFramework("IParentalControlService") {
+ static const FunctionInfo functions[] = {
+ {1, &IParentalControlService::Initialize, "Initialize"},
+ {1001, nullptr, "CheckFreeCommunicationPermission"},
+ {1002, nullptr, "ConfirmLaunchApplicationPermission"},
+ {1003, nullptr, "ConfirmResumeApplicationPermission"},
+ {1004, nullptr, "ConfirmSnsPostPermission"},
+ {1005, nullptr, "ConfirmSystemSettingsPermission"},
+ {1006, nullptr, "IsRestrictionTemporaryUnlocked"},
+ {1007, nullptr, "RevertRestrictionTemporaryUnlocked"},
+ {1008, nullptr, "EnterRestrictedSystemSettings"},
+ {1009, nullptr, "LeaveRestrictedSystemSettings"},
+ {1010, nullptr, "IsRestrictedSystemSettingsEntered"},
+ {1011, nullptr, "RevertRestrictedSystemSettingsEntered"},
+ {1012, nullptr, "GetRestrictedFeatures"},
+ {1013, nullptr, "ConfirmStereoVisionPermission"},
+ {1014, nullptr, "ConfirmPlayableApplicationVideoOld"},
+ {1015, nullptr, "ConfirmPlayableApplicationVideo"},
+ {1031, nullptr, "IsRestrictionEnabled"},
+ {1032, nullptr, "GetSafetyLevel"},
+ {1033, nullptr, "SetSafetyLevel"},
+ {1034, nullptr, "GetSafetyLevelSettings"},
+ {1035, nullptr, "GetCurrentSettings"},
+ {1036, nullptr, "SetCustomSafetyLevelSettings"},
+ {1037, nullptr, "GetDefaultRatingOrganization"},
+ {1038, nullptr, "SetDefaultRatingOrganization"},
+ {1039, nullptr, "GetFreeCommunicationApplicationListCount"},
+ {1042, nullptr, "AddToFreeCommunicationApplicationList"},
+ {1043, nullptr, "DeleteSettings"},
+ {1044, nullptr, "GetFreeCommunicationApplicationList"},
+ {1045, nullptr, "UpdateFreeCommunicationApplicationList"},
+ {1046, nullptr, "DisableFeaturesForReset"},
+ {1047, nullptr, "NotifyApplicationDownloadStarted"},
+ {1061, nullptr, "ConfirmStereoVisionRestrictionConfigurable"},
+ {1062, nullptr, "GetStereoVisionRestriction"},
+ {1063, nullptr, "SetStereoVisionRestriction"},
+ {1064, nullptr, "ResetConfirmedStereoVisionPermission"},
+ {1065, nullptr, "IsStereoVisionPermitted"},
+ {1201, nullptr, "UnlockRestrictionTemporarily"},
+ {1202, nullptr, "UnlockSystemSettingsRestriction"},
+ {1203, nullptr, "SetPinCode"},
+ {1204, nullptr, "GenerateInquiryCode"},
+ {1205, nullptr, "CheckMasterKey"},
+ {1206, nullptr, "GetPinCodeLength"},
+ {1207, nullptr, "GetPinCodeChangedEvent"},
+ {1208, nullptr, "GetPinCode"},
+ {1403, nullptr, "IsPairingActive"},
+ {1406, nullptr, "GetSettingsLastUpdated"},
+ {1411, nullptr, "GetPairingAccountInfo"},
+ {1421, nullptr, "GetAccountNickname"},
+ {1424, nullptr, "GetAccountState"},
+ {1432, nullptr, "GetSynchronizationEvent"},
+ {1451, nullptr, "StartPlayTimer"},
+ {1452, nullptr, "StopPlayTimer"},
+ {1453, nullptr, "IsPlayTimerEnabled"},
+ {1454, nullptr, "GetPlayTimerRemainingTime"},
+ {1455, nullptr, "IsRestrictedByPlayTimer"},
+ {1456, nullptr, "GetPlayTimerSettings"},
+ {1457, nullptr, "GetPlayTimerEventToRequestSuspension"},
+ {1458, nullptr, "IsPlayTimerAlarmDisabled"},
+ {1471, nullptr, "NotifyWrongPinCodeInputManyTimes"},
+ {1472, nullptr, "CancelNetworkRequest"},
+ {1473, nullptr, "GetUnlinkedEvent"},
+ {1474, nullptr, "ClearUnlinkedEvent"},
+ {1601, nullptr, "DisableAllFeatures"},
+ {1602, nullptr, "PostEnableAllFeatures"},
+ {1603, nullptr, "IsAllFeaturesDisabled"},
+ {1901, nullptr, "DeleteFromFreeCommunicationApplicationListForDebug"},
+ {1902, nullptr, "ClearFreeCommunicationApplicationListForDebug"},
+ {1903, nullptr, "GetExemptApplicationListCountForDebug"},
+ {1904, nullptr, "GetExemptApplicationListForDebug"},
+ {1905, nullptr, "UpdateExemptApplicationListForDebug"},
+ {1906, nullptr, "AddToExemptApplicationListForDebug"},
+ {1907, nullptr, "DeleteFromExemptApplicationListForDebug"},
+ {1908, nullptr, "ClearExemptApplicationListForDebug"},
+ {1941, nullptr, "DeletePairing"},
+ {1951, nullptr, "SetPlayTimerSettingsForDebug"},
+ {1952, nullptr, "GetPlayTimerSpentTimeForTest"},
+ {1953, nullptr, "SetPlayTimerAlarmDisabledForDebug"},
+ {2001, nullptr, "RequestPairingAsync"},
+ {2002, nullptr, "FinishRequestPairing"},
+ {2003, nullptr, "AuthorizePairingAsync"},
+ {2004, nullptr, "FinishAuthorizePairing"},
+ {2005, nullptr, "RetrievePairingInfoAsync"},
+ {2006, nullptr, "FinishRetrievePairingInfo"},
+ {2007, nullptr, "UnlinkPairingAsync"},
+ {2008, nullptr, "FinishUnlinkPairing"},
+ {2009, nullptr, "GetAccountMiiImageAsync"},
+ {2010, nullptr, "FinishGetAccountMiiImage"},
+ {2011, nullptr, "GetAccountMiiImageContentTypeAsync"},
+ {2012, nullptr, "FinishGetAccountMiiImageContentType"},
+ {2013, nullptr, "SynchronizeParentalControlSettingsAsync"},
+ {2014, nullptr, "FinishSynchronizeParentalControlSettings"},
+ {2015, nullptr, "FinishSynchronizeParentalControlSettingsWithLastUpdated"},
+ {2016, nullptr, "RequestUpdateExemptionListAsync"},
+ };
+ RegisterHandlers(functions);
+ }
+
+private:
+ void Initialize(Kernel::HLERequestContext& ctx) {
+ NGLOG_WARNING(Service_PCTL, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 2, 0, 0};
+ rb.Push(RESULT_SUCCESS);
+ }
+};
+
+void Module::Interface::CreateService(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IParentalControlService>();
+ NGLOG_DEBUG(Service_PCTL, "called");
+}
+
+void Module::Interface::CreateServiceWithoutInitialize(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IParentalControlService>();
+ NGLOG_DEBUG(Service_PCTL, "called");
+}
+
+Module::Interface::Interface(std::shared_ptr<Module> module, const char* name)
+ : ServiceFramework(name), module(std::move(module)) {}
+
+void InstallInterfaces(SM::ServiceManager& service_manager) {
+ auto module = std::make_shared<Module>();
+ std::make_shared<PCTL>(module, "pctl")->InstallAsService(service_manager);
+ std::make_shared<PCTL>(module, "pctl:a")->InstallAsService(service_manager);
+ std::make_shared<PCTL>(module, "pctl:r")->InstallAsService(service_manager);
+ std::make_shared<PCTL>(module, "pctl:s")->InstallAsService(service_manager);
+}
+
+} // namespace Service::PCTL
diff --git a/src/core/hle/service/pctl/module.h b/src/core/hle/service/pctl/module.h
new file mode 100644
index 000000000..68da628a8
--- /dev/null
+++ b/src/core/hle/service/pctl/module.h
@@ -0,0 +1,28 @@
+// 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/service.h"
+
+namespace Service::PCTL {
+
+class Module final {
+public:
+ class Interface : public ServiceFramework<Interface> {
+ public:
+ Interface(std::shared_ptr<Module> module, const char* name);
+
+ void CreateService(Kernel::HLERequestContext& ctx);
+ void CreateServiceWithoutInitialize(Kernel::HLERequestContext& ctx);
+
+ protected:
+ std::shared_ptr<Module> module;
+ };
+};
+
+/// Registers all PCTL services with the specified service manager.
+void InstallInterfaces(SM::ServiceManager& service_manager);
+
+} // namespace Service::PCTL
diff --git a/src/core/hle/service/pctl/pctl.cpp b/src/core/hle/service/pctl/pctl.cpp
index 692b27a71..de2741d66 100644
--- a/src/core/hle/service/pctl/pctl.cpp
+++ b/src/core/hle/service/pctl/pctl.cpp
@@ -3,14 +3,15 @@
// Refer to the license.txt file included.
#include "core/hle/service/pctl/pctl.h"
-#include "core/hle/service/pctl/pctl_a.h"
-namespace Service {
-namespace PCTL {
+namespace Service::PCTL {
-void InstallInterfaces(SM::ServiceManager& service_manager) {
- std::make_shared<PCTL_A>()->InstallAsService(service_manager);
+PCTL::PCTL(std::shared_ptr<Module> module, const char* name)
+ : Module::Interface(std::move(module), name) {
+ static const FunctionInfo functions[] = {
+ {0, &PCTL::CreateService, "CreateService"},
+ {1, &PCTL::CreateServiceWithoutInitialize, "CreateServiceWithoutInitialize"},
+ };
+ RegisterHandlers(functions);
}
-
-} // namespace PCTL
-} // namespace Service
+} // namespace Service::PCTL
diff --git a/src/core/hle/service/pctl/pctl.h b/src/core/hle/service/pctl/pctl.h
index 5fa67dd1b..8ddf69128 100644
--- a/src/core/hle/service/pctl/pctl.h
+++ b/src/core/hle/service/pctl/pctl.h
@@ -4,13 +4,13 @@
#pragma once
-#include "core/hle/service/service.h"
+#include "core/hle/service/pctl/module.h"
-namespace Service {
-namespace PCTL {
+namespace Service::PCTL {
-/// Registers all PCTL services with the specified service manager.
-void InstallInterfaces(SM::ServiceManager& service_manager);
+class PCTL final : public Module::Interface {
+public:
+ explicit PCTL(std::shared_ptr<Module> module, const char* name);
+};
-} // namespace PCTL
-} // namespace Service
+} // namespace Service::PCTL
diff --git a/src/core/hle/service/pctl/pctl_a.cpp b/src/core/hle/service/pctl/pctl_a.cpp
deleted file mode 100644
index c65fffa07..000000000
--- a/src/core/hle/service/pctl/pctl_a.cpp
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2018 yuzu emulator team
-// 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/pctl/pctl_a.h"
-
-namespace Service {
-namespace PCTL {
-
-class IParentalControlService final : public ServiceFramework<IParentalControlService> {
-public:
- IParentalControlService() : ServiceFramework("IParentalControlService") {}
-};
-
-void PCTL_A::GetService(Kernel::HLERequestContext& ctx) {
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IParentalControlService>();
- LOG_DEBUG(Service_PCTL, "called");
-}
-
-PCTL_A::PCTL_A() : ServiceFramework("pctl:a") {
- static const FunctionInfo functions[] = {
- {0, &PCTL_A::GetService, "GetService"},
- };
- RegisterHandlers(functions);
-}
-
-} // namespace PCTL
-} // namespace Service
diff --git a/src/core/hle/service/pctl/pctl_a.h b/src/core/hle/service/pctl/pctl_a.h
deleted file mode 100644
index a89c8d07d..000000000
--- a/src/core/hle/service/pctl/pctl_a.h
+++ /dev/null
@@ -1,22 +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/service.h"
-
-namespace Service {
-namespace PCTL {
-
-class PCTL_A final : public ServiceFramework<PCTL_A> {
-public:
- PCTL_A();
- ~PCTL_A() = default;
-
-private:
- void GetService(Kernel::HLERequestContext& ctx);
-};
-
-} // namespace PCTL
-} // namespace Service
diff --git a/src/core/hle/service/prepo/prepo.cpp b/src/core/hle/service/prepo/prepo.cpp
new file mode 100644
index 000000000..eaf30ee6b
--- /dev/null
+++ b/src/core/hle/service/prepo/prepo.cpp
@@ -0,0 +1,43 @@
+#include <cinttypes>
+#include "common/logging/log.h"
+#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/event.h"
+#include "core/hle/service/prepo/prepo.h"
+
+namespace Service::PlayReport {
+PlayReport::PlayReport(const char* name) : ServiceFramework(name) {
+ static const FunctionInfo functions[] = {
+ {10100, nullptr, "SaveReport"},
+ {10101, &PlayReport::SaveReportWithUser, "SaveReportWithUser"},
+ {10200, nullptr, "RequestImmediateTransmission"},
+ {10300, nullptr, "GetTransmissionStatus"},
+ {20100, nullptr, "SaveSystemReport"},
+ {20200, nullptr, "SetOperationMode"},
+ {20101, nullptr, "SaveSystemReportWithUser"},
+ {30100, nullptr, "ClearStorage"},
+ {40100, nullptr, "IsUserAgreementCheckEnabled"},
+ {40101, nullptr, "SetUserAgreementCheckEnabled"},
+ {90100, nullptr, "GetStorageUsage"},
+ {90200, nullptr, "GetStatistics"},
+ {90201, nullptr, "GetThroughputHistory"},
+ {90300, nullptr, "GetLastUploadError"},
+ };
+ RegisterHandlers(functions);
+};
+
+void PlayReport::SaveReportWithUser(Kernel::HLERequestContext& ctx) {
+ // TODO(ogniK): Do we want to add play report?
+ NGLOG_WARNING(Service_PREPO, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+};
+
+void InstallInterfaces(SM::ServiceManager& service_manager) {
+ std::make_shared<PlayReport>("prepo:a")->InstallAsService(service_manager);
+ std::make_shared<PlayReport>("prepo:m")->InstallAsService(service_manager);
+ std::make_shared<PlayReport>("prepo:s")->InstallAsService(service_manager);
+ std::make_shared<PlayReport>("prepo:u")->InstallAsService(service_manager);
+}
+
+} // namespace Service::PlayReport
diff --git a/src/core/hle/service/prepo/prepo.h b/src/core/hle/service/prepo/prepo.h
new file mode 100644
index 000000000..3708e0dcb
--- /dev/null
+++ b/src/core/hle/service/prepo/prepo.h
@@ -0,0 +1,23 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <memory>
+#include <string>
+#include "core/hle/kernel/event.h"
+#include "core/hle/service/service.h"
+
+namespace Service::PlayReport {
+
+class PlayReport final : public ServiceFramework<PlayReport> {
+public:
+ explicit PlayReport(const char* name);
+ ~PlayReport() = default;
+
+private:
+ void SaveReportWithUser(Kernel::HLERequestContext& ctx);
+};
+
+void InstallInterfaces(SM::ServiceManager& service_manager);
+
+} // namespace Service::PlayReport
diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp
index 6a2d6a4ef..bdd9eb5a5 100644
--- a/src/core/hle/service/service.cpp
+++ b/src/core/hle/service/service.cpp
@@ -7,6 +7,7 @@
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/string_util.h"
+#include "core/core.h"
#include "core/hle/ipc.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/client_port.h"
@@ -19,19 +20,26 @@
#include "core/hle/service/aoc/aoc_u.h"
#include "core/hle/service/apm/apm.h"
#include "core/hle/service/audio/audio.h"
+#include "core/hle/service/bcat/bcat.h"
+#include "core/hle/service/fatal/fatal.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/friend/friend.h"
#include "core/hle/service/hid/hid.h"
#include "core/hle/service/lm/lm.h"
+#include "core/hle/service/mm/mm_u.h"
+#include "core/hle/service/nfp/nfp.h"
#include "core/hle/service/nifm/nifm.h"
#include "core/hle/service/ns/ns.h"
#include "core/hle/service/nvdrv/nvdrv.h"
#include "core/hle/service/pctl/pctl.h"
+#include "core/hle/service/prepo/prepo.h"
#include "core/hle/service/service.h"
-#include "core/hle/service/set/set.h"
+#include "core/hle/service/set/settings.h"
#include "core/hle/service/sm/controller.h"
#include "core/hle/service/sm/sm.h"
#include "core/hle/service/sockets/sockets.h"
+#include "core/hle/service/spl/module.h"
+#include "core/hle/service/ssl/ssl.h"
#include "core/hle/service/time/time.h"
#include "core/hle/service/vi/vi.h"
@@ -52,10 +60,9 @@ static std::string MakeFunctionString(const char* name, const char* port_name,
// Number of params == bits 0-5 + bits 6-11
int num_params = (cmd_buff[0] & 0x3F) + ((cmd_buff[0] >> 6) & 0x3F);
- std::string function_string =
- Common::StringFromFormat("function '%s': port=%s", name, port_name);
+ std::string function_string = fmt::format("function '{}': port={}", name, port_name);
for (int i = 1; i <= num_params; ++i) {
- function_string += Common::StringFromFormat(", cmd_buff[%i]=0x%X", i, cmd_buff[i]);
+ function_string += fmt::format(", cmd_buff[{}]=0x{:X}", i, cmd_buff[i]);
}
return function_string;
}
@@ -107,15 +114,15 @@ void ServiceFrameworkBase::ReportUnimplementedFunction(Kernel::HLERequestContext
auto cmd_buf = ctx.CommandBuffer();
std::string function_name = info == nullptr ? fmt::format("{}", ctx.GetCommand()) : info->name;
- fmt::MemoryWriter w;
- w.write("function '{}': port='{}' cmd_buf={{[0]={:#x}", function_name, service_name,
- cmd_buf[0]);
+ fmt::memory_buffer buf;
+ fmt::format_to(buf, "function '{}': port='{}' cmd_buf={{[0]=0x{:X}", function_name,
+ service_name, cmd_buf[0]);
for (int i = 1; i <= 8; ++i) {
- w.write(", [{}]={:#x}", i, cmd_buf[i]);
+ fmt::format_to(buf, ", [{}]=0x{:X}", i, cmd_buf[i]);
}
- w << '}';
+ buf.push_back('}');
- LOG_ERROR(Service, "unknown / unimplemented %s", w.c_str());
+ NGLOG_ERROR(Service, "unknown / unimplemented {}", fmt::to_string(buf));
UNIMPLEMENTED();
}
@@ -126,8 +133,8 @@ void ServiceFrameworkBase::InvokeRequest(Kernel::HLERequestContext& ctx) {
return ReportUnimplementedFunction(ctx, info);
}
- LOG_TRACE(
- Service, "%s",
+ NGLOG_TRACE(
+ Service, "{}",
MakeFunctionString(info->name, GetServiceName().c_str(), ctx.CommandBuffer()).c_str());
handler_invoker(this, info->handler_callback, ctx);
}
@@ -139,21 +146,21 @@ ResultCode ServiceFrameworkBase::HandleSyncRequest(Kernel::HLERequestContext& co
rb.Push(RESULT_SUCCESS);
return ResultCode(ErrorModule::HIPC, ErrorDescription::RemoteProcessDead);
}
+ case IPC::CommandType::ControlWithContext:
case IPC::CommandType::Control: {
- SM::g_service_manager->InvokeControlRequest(context);
+ Core::System::GetInstance().ServiceManager().InvokeControlRequest(context);
break;
}
+ case IPC::CommandType::RequestWithContext:
case IPC::CommandType::Request: {
InvokeRequest(context);
break;
}
default:
- UNIMPLEMENTED_MSG("command_type=%d", context.GetCommandType());
+ UNIMPLEMENTED_MSG("command_type={}", static_cast<int>(context.GetCommandType()));
}
- u32* cmd_buf = (u32*)Memory::GetPointer(Kernel::GetCurrentThread()->GetTLSAddress());
- context.WriteToOutgoingCommandBuffer(cmd_buf, *Kernel::g_current_process,
- Kernel::g_handle_table);
+ context.WriteToOutgoingCommandBuffer(*Kernel::GetCurrentThread());
return RESULT_SUCCESS;
}
@@ -167,39 +174,44 @@ void AddNamedPort(std::string name, SharedPtr<ClientPort> port) {
}
/// Initialize ServiceManager
-void Init() {
+void Init(std::shared_ptr<SM::ServiceManager>& sm) {
// NVFlinger needs to be accessed by several services like Vi and AppletOE so we instantiate it
// here and pass it into the respective InstallInterfaces functions.
auto nv_flinger = std::make_shared<NVFlinger::NVFlinger>();
- SM::g_service_manager = std::make_shared<SM::ServiceManager>();
- SM::ServiceManager::InstallInterfaces(SM::g_service_manager);
-
- Account::InstallInterfaces(*SM::g_service_manager);
- AM::InstallInterfaces(*SM::g_service_manager, nv_flinger);
- AOC::InstallInterfaces(*SM::g_service_manager);
- APM::InstallInterfaces(*SM::g_service_manager);
- Audio::InstallInterfaces(*SM::g_service_manager);
- FileSystem::InstallInterfaces(*SM::g_service_manager);
- Friend::InstallInterfaces(*SM::g_service_manager);
- HID::InstallInterfaces(*SM::g_service_manager);
- LM::InstallInterfaces(*SM::g_service_manager);
- NIFM::InstallInterfaces(*SM::g_service_manager);
- NS::InstallInterfaces(*SM::g_service_manager);
- Nvidia::InstallInterfaces(*SM::g_service_manager);
- PCTL::InstallInterfaces(*SM::g_service_manager);
- Sockets::InstallInterfaces(*SM::g_service_manager);
- Time::InstallInterfaces(*SM::g_service_manager);
- VI::InstallInterfaces(*SM::g_service_manager, nv_flinger);
- Set::InstallInterfaces(*SM::g_service_manager);
-
- LOG_DEBUG(Service, "initialized OK");
+ SM::ServiceManager::InstallInterfaces(sm);
+
+ Account::InstallInterfaces(*sm);
+ AM::InstallInterfaces(*sm, nv_flinger);
+ AOC::InstallInterfaces(*sm);
+ APM::InstallInterfaces(*sm);
+ BCAT::InstallInterfaces(*sm);
+ Audio::InstallInterfaces(*sm);
+ Fatal::InstallInterfaces(*sm);
+ FileSystem::InstallInterfaces(*sm);
+ Friend::InstallInterfaces(*sm);
+ HID::InstallInterfaces(*sm);
+ LM::InstallInterfaces(*sm);
+ MM::InstallInterfaces(*sm);
+ NFP::InstallInterfaces(*sm);
+ NIFM::InstallInterfaces(*sm);
+ NS::InstallInterfaces(*sm);
+ Nvidia::InstallInterfaces(*sm);
+ PCTL::InstallInterfaces(*sm);
+ PlayReport::InstallInterfaces(*sm);
+ Sockets::InstallInterfaces(*sm);
+ SPL::InstallInterfaces(*sm);
+ SSL::InstallInterfaces(*sm);
+ Time::InstallInterfaces(*sm);
+ VI::InstallInterfaces(*sm, nv_flinger);
+ Set::InstallInterfaces(*sm);
+
+ NGLOG_DEBUG(Service, "initialized OK");
}
/// Shutdown ServiceManager
void Shutdown() {
- SM::g_service_manager = nullptr;
g_kernel_named_ports.clear();
- LOG_DEBUG(Service, "shutdown OK");
+ NGLOG_DEBUG(Service, "shutdown OK");
}
} // namespace Service
diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h
index 9c2e826da..fee841d46 100644
--- a/src/core/hle/service/service.h
+++ b/src/core/hle/service/service.h
@@ -178,7 +178,7 @@ private:
};
/// Initialize ServiceManager
-void Init();
+void Init(std::shared_ptr<SM::ServiceManager>& sm);
/// Shutdown ServiceManager
void Shutdown();
diff --git a/src/core/hle/service/set/set.cpp b/src/core/hle/service/set/set.cpp
index 3001ee411..f0572bed6 100644
--- a/src/core/hle/service/set/set.cpp
+++ b/src/core/hle/service/set/set.cpp
@@ -9,33 +9,53 @@
#include "core/hle/kernel/client_session.h"
#include "core/hle/service/set/set.h"
-namespace Service {
-namespace Set {
+namespace Service::Set {
void SET::GetAvailableLanguageCodes(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
u32 id = rp.Pop<u32>();
- constexpr std::array<u8, 13> lang_codes{};
-
- ctx.WriteBuffer(lang_codes.data(), lang_codes.size());
-
- IPC::ResponseBuilder rb{ctx, 2};
+ static constexpr std::array<LanguageCode, 17> available_language_codes = {{
+ LanguageCode::JA,
+ LanguageCode::EN_US,
+ LanguageCode::FR,
+ LanguageCode::DE,
+ LanguageCode::IT,
+ LanguageCode::ES,
+ LanguageCode::ZH_CN,
+ LanguageCode::KO,
+ LanguageCode::NL,
+ LanguageCode::PT,
+ LanguageCode::RU,
+ LanguageCode::ZH_TW,
+ LanguageCode::EN_GB,
+ LanguageCode::FR_CA,
+ LanguageCode::ES_419,
+ LanguageCode::ZH_HANS,
+ LanguageCode::ZH_HANT,
+ }};
+ ctx.WriteBuffer(available_language_codes.data(), available_language_codes.size());
+
+ IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
+ rb.Push(static_cast<u64>(available_language_codes.size()));
- LOG_WARNING(Service_SET, "(STUBBED) called");
+ NGLOG_DEBUG(Service_SET, "called");
}
-SET::SET(const char* name) : ServiceFramework(name) {
+SET::SET() : ServiceFramework("set") {
static const FunctionInfo functions[] = {
+ {0, nullptr, "GetLanguageCode"},
{1, &SET::GetAvailableLanguageCodes, "GetAvailableLanguageCodes"},
+ {2, nullptr, "MakeLanguageCode"},
+ {3, nullptr, "GetAvailableLanguageCodeCount"},
+ {4, nullptr, "GetRegionCode"},
+ {5, nullptr, "GetAvailableLanguageCodes2"},
+ {6, nullptr, "GetAvailableLanguageCodeCount2"},
+ {7, nullptr, "GetKeyCodeMap"},
+ {8, nullptr, "GetQuestFlag"},
};
RegisterHandlers(functions);
}
-void InstallInterfaces(SM::ServiceManager& service_manager) {
- std::make_shared<SET>("set")->InstallAsService(service_manager);
-}
-
-} // namespace Set
-} // namespace Service
+} // namespace Service::Set
diff --git a/src/core/hle/service/set/set.h b/src/core/hle/service/set/set.h
index 61e957946..ec0df0152 100644
--- a/src/core/hle/service/set/set.h
+++ b/src/core/hle/service/set/set.h
@@ -6,20 +6,36 @@
#include "core/hle/service/service.h"
-namespace Service {
-namespace Set {
+namespace Service::Set {
+
+/// This is "nn::settings::LanguageCode", which is a NUL-terminated string stored in a u64.
+enum class LanguageCode : u64 {
+ JA = 0x000000000000616A,
+ EN_US = 0x00000053552D6E65,
+ FR = 0x0000000000007266,
+ DE = 0x0000000000006564,
+ IT = 0x0000000000007469,
+ ES = 0x0000000000007365,
+ ZH_CN = 0x0000004E432D687A,
+ KO = 0x0000000000006F6B,
+ NL = 0x0000000000006C6E,
+ PT = 0x0000000000007470,
+ RU = 0x0000000000007572,
+ ZH_TW = 0x00000057542D687A,
+ EN_GB = 0x00000042472D6E65,
+ FR_CA = 0x00000041432D7266,
+ ES_419 = 0x00003931342D7365,
+ ZH_HANS = 0x00736E61482D687A,
+ ZH_HANT = 0x00746E61482D687A,
+};
class SET final : public ServiceFramework<SET> {
public:
- explicit SET(const char* name);
+ explicit SET();
~SET() = default;
private:
void GetAvailableLanguageCodes(Kernel::HLERequestContext& ctx);
};
-/// Registers all Set services with the specified service manager.
-void InstallInterfaces(SM::ServiceManager& service_manager);
-
-} // namespace Set
-} // namespace Service
+} // namespace Service::Set
diff --git a/src/core/hle/service/set/set_cal.cpp b/src/core/hle/service/set/set_cal.cpp
new file mode 100644
index 000000000..7066ef725
--- /dev/null
+++ b/src/core/hle/service/set/set_cal.cpp
@@ -0,0 +1,47 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/set/set_cal.h"
+
+namespace Service::Set {
+
+SET_CAL::SET_CAL() : ServiceFramework("set:cal") {
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "GetBluetoothBdAddress"},
+ {1, nullptr, "GetConfigurationId1"},
+ {2, nullptr, "GetAccelerometerOffset"},
+ {3, nullptr, "GetAccelerometerScale"},
+ {4, nullptr, "GetGyroscopeOffset"},
+ {5, nullptr, "GetGyroscopeScale"},
+ {6, nullptr, "GetWirelessLanMacAddress"},
+ {7, nullptr, "GetWirelessLanCountryCodeCount"},
+ {8, nullptr, "GetWirelessLanCountryCodes"},
+ {9, nullptr, "GetSerialNumber"},
+ {10, nullptr, "SetInitialSystemAppletProgramId"},
+ {11, nullptr, "SetOverlayDispProgramId"},
+ {12, nullptr, "GetBatteryLot"},
+ {14, nullptr, "GetEciDeviceCertificate"},
+ {15, nullptr, "GetEticketDeviceCertificate"},
+ {16, nullptr, "GetSslKey"},
+ {17, nullptr, "GetSslCertificate"},
+ {18, nullptr, "GetGameCardKey"},
+ {19, nullptr, "GetGameCardCertificate"},
+ {20, nullptr, "GetEciDeviceKey"},
+ {21, nullptr, "GetEticketDeviceKey"},
+ {22, nullptr, "GetSpeakerParameter"},
+ {23, nullptr, "GetLcdVendorId"},
+ {24, nullptr, "GetEciDeviceCertificate2"},
+ {25, nullptr, "GetEciDeviceKey2"},
+ {26, nullptr, "GetAmiiboKey"},
+ {27, nullptr, "GetAmiiboEcqvCertificate"},
+ {28, nullptr, "GetAmiiboEcdsaCertificate"},
+ {29, nullptr, "GetAmiiboEcqvBlsKey"},
+ {30, nullptr, "GetAmiiboEcqvBlsCertificate"},
+ {31, nullptr, "GetAmiiboEcqvBlsRootCertificate"},
+ {32, nullptr, "GetUnknownId"},
+ };
+ RegisterHandlers(functions);
+}
+
+} // namespace Service::Set
diff --git a/src/core/hle/service/set/set_cal.h b/src/core/hle/service/set/set_cal.h
new file mode 100644
index 000000000..bb50336aa
--- /dev/null
+++ b/src/core/hle/service/set/set_cal.h
@@ -0,0 +1,17 @@
+// 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/service.h"
+
+namespace Service::Set {
+
+class SET_CAL final : public ServiceFramework<SET_CAL> {
+public:
+ explicit SET_CAL();
+ ~SET_CAL() = default;
+};
+
+} // namespace Service::Set
diff --git a/src/core/hle/service/set/set_fd.cpp b/src/core/hle/service/set/set_fd.cpp
new file mode 100644
index 000000000..c9f938716
--- /dev/null
+++ b/src/core/hle/service/set/set_fd.cpp
@@ -0,0 +1,23 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/set/set_fd.h"
+
+namespace Service::Set {
+
+SET_FD::SET_FD() : ServiceFramework("set:fd") {
+ static const FunctionInfo functions[] = {
+ {2, nullptr, "SetSettingsItemValue"},
+ {3, nullptr, "ResetSettingsItemValue"},
+ {4, nullptr, "CreateSettingsItemKeyIterator"},
+ {10, nullptr, "ReadSettings"},
+ {11, nullptr, "ResetSettings"},
+ {20, nullptr, "SetWebInspectorFlag"},
+ {21, nullptr, "SetAllowedSslHosts"},
+ {22, nullptr, "SetHostFsMountPoint"},
+ };
+ RegisterHandlers(functions);
+}
+
+} // namespace Service::Set
diff --git a/src/core/hle/service/set/set_fd.h b/src/core/hle/service/set/set_fd.h
new file mode 100644
index 000000000..dbd850bc7
--- /dev/null
+++ b/src/core/hle/service/set/set_fd.h
@@ -0,0 +1,17 @@
+// 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/service.h"
+
+namespace Service::Set {
+
+class SET_FD final : public ServiceFramework<SET_FD> {
+public:
+ explicit SET_FD();
+ ~SET_FD() = default;
+};
+
+} // namespace Service::Set
diff --git a/src/core/hle/service/set/set_sys.cpp b/src/core/hle/service/set/set_sys.cpp
new file mode 100644
index 000000000..762a664c5
--- /dev/null
+++ b/src/core/hle/service/set/set_sys.cpp
@@ -0,0 +1,175 @@
+// Copyright 2018 yuzu emulator team
+// 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/client_port.h"
+#include "core/hle/service/set/set_sys.h"
+
+namespace Service::Set {
+
+void SET_SYS::GetColorSetId(Kernel::HLERequestContext& ctx) {
+
+ IPC::ResponseBuilder rb{ctx, 3};
+
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(0);
+
+ NGLOG_WARNING(Service_SET, "(STUBBED) called");
+}
+
+SET_SYS::SET_SYS() : ServiceFramework("set:sys") {
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "SetLanguageCode"},
+ {1, nullptr, "SetNetworkSettings"},
+ {2, nullptr, "GetNetworkSettings"},
+ {3, nullptr, "GetFirmwareVersion"},
+ {4, nullptr, "GetFirmwareVersion2"},
+ {5, nullptr, "GetFirmwareVersionDigest"},
+ {7, nullptr, "GetLockScreenFlag"},
+ {8, nullptr, "SetLockScreenFlag"},
+ {9, nullptr, "GetBacklightSettings"},
+ {10, nullptr, "SetBacklightSettings"},
+ {11, nullptr, "SetBluetoothDevicesSettings"},
+ {12, nullptr, "GetBluetoothDevicesSettings"},
+ {13, nullptr, "GetExternalSteadyClockSourceId"},
+ {14, nullptr, "SetExternalSteadyClockSourceId"},
+ {15, nullptr, "GetUserSystemClockContext"},
+ {16, nullptr, "SetUserSystemClockContext"},
+ {17, nullptr, "GetAccountSettings"},
+ {18, nullptr, "SetAccountSettings"},
+ {19, nullptr, "GetAudioVolume"},
+ {20, nullptr, "SetAudioVolume"},
+ {21, nullptr, "GetEulaVersions"},
+ {22, nullptr, "SetEulaVersions"},
+ {23, &SET_SYS::GetColorSetId, "GetColorSetId"},
+ {24, nullptr, "SetColorSetId"},
+ {25, nullptr, "GetConsoleInformationUploadFlag"},
+ {26, nullptr, "SetConsoleInformationUploadFlag"},
+ {27, nullptr, "GetAutomaticApplicationDownloadFlag"},
+ {28, nullptr, "SetAutomaticApplicationDownloadFlag"},
+ {29, nullptr, "GetNotificationSettings"},
+ {30, nullptr, "SetNotificationSettings"},
+ {31, nullptr, "GetAccountNotificationSettings"},
+ {32, nullptr, "SetAccountNotificationSettings"},
+ {35, nullptr, "GetVibrationMasterVolume"},
+ {36, nullptr, "SetVibrationMasterVolume"},
+ {37, nullptr, "GetSettingsItemValueSize"},
+ {38, nullptr, "GetSettingsItemValue"},
+ {39, nullptr, "GetTvSettings"},
+ {40, nullptr, "SetTvSettings"},
+ {41, nullptr, "GetEdid"},
+ {42, nullptr, "SetEdid"},
+ {43, nullptr, "GetAudioOutputMode"},
+ {44, nullptr, "SetAudioOutputMode"},
+ {45, nullptr, "IsForceMuteOnHeadphoneRemoved"},
+ {46, nullptr, "SetForceMuteOnHeadphoneRemoved"},
+ {47, nullptr, "GetQuestFlag"},
+ {48, nullptr, "SetQuestFlag"},
+ {49, nullptr, "GetDataDeletionSettings"},
+ {50, nullptr, "SetDataDeletionSettings"},
+ {51, nullptr, "GetInitialSystemAppletProgramId"},
+ {52, nullptr, "GetOverlayDispProgramId"},
+ {53, nullptr, "GetDeviceTimeZoneLocationName"},
+ {54, nullptr, "SetDeviceTimeZoneLocationName"},
+ {55, nullptr, "GetWirelessCertificationFileSize"},
+ {56, nullptr, "GetWirelessCertificationFile"},
+ {57, nullptr, "SetRegionCode"},
+ {58, nullptr, "GetNetworkSystemClockContext"},
+ {59, nullptr, "SetNetworkSystemClockContext"},
+ {60, nullptr, "IsUserSystemClockAutomaticCorrectionEnabled"},
+ {61, nullptr, "SetUserSystemClockAutomaticCorrectionEnabled"},
+ {62, nullptr, "GetDebugModeFlag"},
+ {63, nullptr, "GetPrimaryAlbumStorage"},
+ {64, nullptr, "SetPrimaryAlbumStorage"},
+ {65, nullptr, "GetUsb30EnableFlag"},
+ {66, nullptr, "SetUsb30EnableFlag"},
+ {67, nullptr, "GetBatteryLot"},
+ {68, nullptr, "GetSerialNumber"},
+ {69, nullptr, "GetNfcEnableFlag"},
+ {70, nullptr, "SetNfcEnableFlag"},
+ {71, nullptr, "GetSleepSettings"},
+ {72, nullptr, "SetSleepSettings"},
+ {73, nullptr, "GetWirelessLanEnableFlag"},
+ {74, nullptr, "SetWirelessLanEnableFlag"},
+ {75, nullptr, "GetInitialLaunchSettings"},
+ {76, nullptr, "SetInitialLaunchSettings"},
+ {77, nullptr, "GetDeviceNickName"},
+ {78, nullptr, "SetDeviceNickName"},
+ {79, nullptr, "GetProductModel"},
+ {80, nullptr, "GetLdnChannel"},
+ {81, nullptr, "SetLdnChannel"},
+ {82, nullptr, "AcquireTelemetryDirtyFlagEventHandle"},
+ {83, nullptr, "GetTelemetryDirtyFlags"},
+ {84, nullptr, "GetPtmBatteryLot"},
+ {85, nullptr, "SetPtmBatteryLot"},
+ {86, nullptr, "GetPtmFuelGaugeParameter"},
+ {87, nullptr, "SetPtmFuelGaugeParameter"},
+ {88, nullptr, "GetBluetoothEnableFlag"},
+ {89, nullptr, "SetBluetoothEnableFlag"},
+ {90, nullptr, "GetMiiAuthorId"},
+ {91, nullptr, "SetShutdownRtcValue"},
+ {92, nullptr, "GetShutdownRtcValue"},
+ {93, nullptr, "AcquireFatalDirtyFlagEventHandle"},
+ {94, nullptr, "GetFatalDirtyFlags"},
+ {95, nullptr, "GetAutoUpdateEnableFlag"},
+ {96, nullptr, "SetAutoUpdateEnableFlag"},
+ {97, nullptr, "GetNxControllerSettings"},
+ {98, nullptr, "SetNxControllerSettings"},
+ {99, nullptr, "GetBatteryPercentageFlag"},
+ {100, nullptr, "SetBatteryPercentageFlag"},
+ {101, nullptr, "GetExternalRtcResetFlag"},
+ {102, nullptr, "SetExternalRtcResetFlag"},
+ {103, nullptr, "GetUsbFullKeyEnableFlag"},
+ {104, nullptr, "SetUsbFullKeyEnableFlag"},
+ {105, nullptr, "SetExternalSteadyClockInternalOffset"},
+ {106, nullptr, "GetExternalSteadyClockInternalOffset"},
+ {107, nullptr, "GetBacklightSettingsEx"},
+ {108, nullptr, "SetBacklightSettingsEx"},
+ {109, nullptr, "GetHeadphoneVolumeWarningCount"},
+ {110, nullptr, "SetHeadphoneVolumeWarningCount"},
+ {111, nullptr, "GetBluetoothAfhEnableFlag"},
+ {112, nullptr, "SetBluetoothAfhEnableFlag"},
+ {113, nullptr, "GetBluetoothBoostEnableFlag"},
+ {114, nullptr, "SetBluetoothBoostEnableFlag"},
+ {115, nullptr, "GetInRepairProcessEnableFlag"},
+ {116, nullptr, "SetInRepairProcessEnableFlag"},
+ {117, nullptr, "GetHeadphoneVolumeUpdateFlag"},
+ {118, nullptr, "SetHeadphoneVolumeUpdateFlag"},
+ {119, nullptr, "NeedsToUpdateHeadphoneVolume"},
+ {120, nullptr, "GetPushNotificationActivityModeOnSleep"},
+ {121, nullptr, "SetPushNotificationActivityModeOnSleep"},
+ {122, nullptr, "GetServiceDiscoveryControlSettings"},
+ {123, nullptr, "SetServiceDiscoveryControlSettings"},
+ {124, nullptr, "GetErrorReportSharePermission"},
+ {125, nullptr, "SetErrorReportSharePermission"},
+ {126, nullptr, "GetAppletLaunchFlags"},
+ {127, nullptr, "SetAppletLaunchFlags"},
+ {128, nullptr, "GetConsoleSixAxisSensorAccelerationBias"},
+ {129, nullptr, "SetConsoleSixAxisSensorAccelerationBias"},
+ {130, nullptr, "GetConsoleSixAxisSensorAngularVelocityBias"},
+ {131, nullptr, "SetConsoleSixAxisSensorAngularVelocityBias"},
+ {132, nullptr, "GetConsoleSixAxisSensorAccelerationGain"},
+ {133, nullptr, "SetConsoleSixAxisSensorAccelerationGain"},
+ {134, nullptr, "GetConsoleSixAxisSensorAngularVelocityGain"},
+ {135, nullptr, "SetConsoleSixAxisSensorAngularVelocityGain"},
+ {136, nullptr, "GetKeyboardLayout"},
+ {137, nullptr, "SetKeyboardLayout"},
+ {138, nullptr, "GetWebInspectorFlag"},
+ {139, nullptr, "GetAllowedSslHosts"},
+ {140, nullptr, "GetHostFsMountPoint"},
+ {141, nullptr, "GetRequiresRunRepairTimeReviser"},
+ {142, nullptr, "SetRequiresRunRepairTimeReviser"},
+ {143, nullptr, "SetBlePairingSettings"},
+ {144, nullptr, "GetBlePairingSettings"},
+ {145, nullptr, "GetConsoleSixAxisSensorAngularVelocityTimeBias"},
+ {146, nullptr, "SetConsoleSixAxisSensorAngularVelocityTimeBias"},
+ {147, nullptr, "GetConsoleSixAxisSensorAngularAcceleration"},
+ {148, nullptr, "SetConsoleSixAxisSensorAngularAcceleration"},
+ {149, nullptr, "GetRebootlessSystemUpdateVersion"},
+ };
+ RegisterHandlers(functions);
+}
+
+} // namespace Service::Set
diff --git a/src/core/hle/service/set/set_sys.h b/src/core/hle/service/set/set_sys.h
new file mode 100644
index 000000000..b77a97cde
--- /dev/null
+++ b/src/core/hle/service/set/set_sys.h
@@ -0,0 +1,20 @@
+// 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/service.h"
+
+namespace Service::Set {
+
+class SET_SYS final : public ServiceFramework<SET_SYS> {
+public:
+ explicit SET_SYS();
+ ~SET_SYS() = default;
+
+private:
+ void GetColorSetId(Kernel::HLERequestContext& ctx);
+};
+
+} // namespace Service::Set
diff --git a/src/core/hle/service/set/settings.cpp b/src/core/hle/service/set/settings.cpp
new file mode 100644
index 000000000..cf5541ca8
--- /dev/null
+++ b/src/core/hle/service/set/settings.cpp
@@ -0,0 +1,20 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/set/set.h"
+#include "core/hle/service/set/set_cal.h"
+#include "core/hle/service/set/set_fd.h"
+#include "core/hle/service/set/set_sys.h"
+#include "core/hle/service/set/settings.h"
+
+namespace Service::Set {
+
+void InstallInterfaces(SM::ServiceManager& service_manager) {
+ std::make_shared<SET>()->InstallAsService(service_manager);
+ std::make_shared<SET_CAL>()->InstallAsService(service_manager);
+ std::make_shared<SET_FD>()->InstallAsService(service_manager);
+ std::make_shared<SET_SYS>()->InstallAsService(service_manager);
+}
+
+} // namespace Service::Set
diff --git a/src/core/hle/service/set/settings.h b/src/core/hle/service/set/settings.h
new file mode 100644
index 000000000..6606ce776
--- /dev/null
+++ b/src/core/hle/service/set/settings.h
@@ -0,0 +1,14 @@
+// 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/service.h"
+
+namespace Service::Set {
+
+/// Registers all Settings services with the specified service manager.
+void InstallInterfaces(SM::ServiceManager& service_manager);
+
+} // namespace Service::Set
diff --git a/src/core/hle/service/sm/controller.cpp b/src/core/hle/service/sm/controller.cpp
index e12c53442..fe5097cdc 100644
--- a/src/core/hle/service/sm/controller.cpp
+++ b/src/core/hle/service/sm/controller.cpp
@@ -7,8 +7,7 @@
#include "core/hle/kernel/session.h"
#include "core/hle/service/sm/controller.h"
-namespace Service {
-namespace SM {
+namespace Service::SM {
void Controller::ConvertSessionToDomain(Kernel::HLERequestContext& ctx) {
ASSERT_MSG(!ctx.Session()->IsDomain(), "session is alread a domain");
@@ -18,7 +17,7 @@ void Controller::ConvertSessionToDomain(Kernel::HLERequestContext& ctx) {
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(1); // Converted sessions start with 1 request handler
- LOG_DEBUG(Service, "called, server_session=%d", ctx.Session()->GetObjectId());
+ NGLOG_DEBUG(Service, "called, server_session={}", ctx.Session()->GetObjectId());
}
void Controller::DuplicateSession(Kernel::HLERequestContext& ctx) {
@@ -30,11 +29,11 @@ void Controller::DuplicateSession(Kernel::HLERequestContext& ctx) {
Kernel::SharedPtr<Kernel::ClientSession> session{ctx.Session()->parent->client};
rb.PushMoveObjects(session);
- LOG_DEBUG(Service, "called, session=%u", session->GetObjectId());
+ NGLOG_DEBUG(Service, "called, session={}", session->GetObjectId());
}
void Controller::DuplicateSessionEx(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service, "(STUBBED) called, using DuplicateSession");
+ NGLOG_WARNING(Service, "(STUBBED) called, using DuplicateSession");
DuplicateSession(ctx);
}
@@ -44,7 +43,7 @@ void Controller::QueryPointerBufferSize(Kernel::HLERequestContext& ctx) {
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(0x500);
- LOG_WARNING(Service, "(STUBBED) called");
+ NGLOG_WARNING(Service, "(STUBBED) called");
}
Controller::Controller() : ServiceFramework("IpcController") {
@@ -58,5 +57,4 @@ Controller::Controller() : ServiceFramework("IpcController") {
RegisterHandlers(functions);
}
-} // namespace SM
-} // namespace Service
+} // namespace Service::SM
diff --git a/src/core/hle/service/sm/controller.h b/src/core/hle/service/sm/controller.h
index 7b4bc4b75..a4de52cd2 100644
--- a/src/core/hle/service/sm/controller.h
+++ b/src/core/hle/service/sm/controller.h
@@ -6,8 +6,7 @@
#include "core/hle/service/service.h"
-namespace Service {
-namespace SM {
+namespace Service::SM {
class Controller final : public ServiceFramework<Controller> {
public:
@@ -21,5 +20,4 @@ private:
void QueryPointerBufferSize(Kernel::HLERequestContext& ctx);
};
-} // namespace SM
-} // namespace Service
+} // namespace Service::SM
diff --git a/src/core/hle/service/sm/sm.cpp b/src/core/hle/service/sm/sm.cpp
index bc72512a0..bded8421f 100644
--- a/src/core/hle/service/sm/sm.cpp
+++ b/src/core/hle/service/sm/sm.cpp
@@ -12,8 +12,9 @@
#include "core/hle/service/sm/controller.h"
#include "core/hle/service/sm/sm.h"
-namespace Service {
-namespace SM {
+namespace Service::SM {
+
+ServiceManager::~ServiceManager() = default;
void ServiceManager::InvokeControlRequest(Kernel::HLERequestContext& context) {
controller_interface->InvokeRequest(context);
@@ -73,7 +74,7 @@ ResultVal<Kernel::SharedPtr<Kernel::ClientSession>> ServiceManager::ConnectToSer
return client_port->Connect();
}
-std::shared_ptr<ServiceManager> g_service_manager;
+SM::~SM() = default;
/**
* SM::Initialize service function
@@ -85,7 +86,7 @@ std::shared_ptr<ServiceManager> g_service_manager;
void SM::Initialize(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_DEBUG(Service_SM, "called");
+ NGLOG_DEBUG(Service_SM, "called");
}
void SM::GetService(Kernel::HLERequestContext& ctx) {
@@ -101,8 +102,8 @@ void SM::GetService(Kernel::HLERequestContext& ctx) {
if (client_port.Failed()) {
IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0);
rb.Push(client_port.Code());
- LOG_ERROR(Service_SM, "called service=%s -> error 0x%08X", name.c_str(),
- client_port.Code().raw);
+ NGLOG_ERROR(Service_SM, "called service={} -> error 0x{:08X}", name,
+ client_port.Code().raw);
if (name.length() == 0)
return; // LibNX Fix
UNIMPLEMENTED();
@@ -112,8 +113,7 @@ void SM::GetService(Kernel::HLERequestContext& ctx) {
auto session = client_port.Unwrap()->Connect();
ASSERT(session.Succeeded());
if (session.Succeeded()) {
- LOG_DEBUG(Service_SM, "called service=%s -> session=%u", name.c_str(),
- (*session)->GetObjectId());
+ NGLOG_DEBUG(Service_SM, "called service={} -> session={}", name, (*session)->GetObjectId());
IPC::ResponseBuilder rb =
rp.MakeBuilder(2, 0, 1, IPC::ResponseBuilder::Flags::AlwaysMoveHandles);
rb.Push(session.Code());
@@ -132,5 +132,4 @@ SM::SM(std::shared_ptr<ServiceManager> service_manager)
RegisterHandlers(functions);
}
-} // namespace SM
-} // namespace Service
+} // namespace Service::SM
diff --git a/src/core/hle/service/sm/sm.h b/src/core/hle/service/sm/sm.h
index 11fa788ca..13f5c4c28 100644
--- a/src/core/hle/service/sm/sm.h
+++ b/src/core/hle/service/sm/sm.h
@@ -17,14 +17,13 @@ class ServerPort;
class SessionRequestHandler;
} // namespace Kernel
-namespace Service {
-namespace SM {
+namespace Service::SM {
/// Interface to "sm:" service
class SM final : public ServiceFramework<SM> {
public:
SM(std::shared_ptr<ServiceManager> service_manager);
- ~SM() = default;
+ ~SM() override;
private:
void Initialize(Kernel::HLERequestContext& ctx);
@@ -45,6 +44,8 @@ class ServiceManager {
public:
static void InstallInterfaces(std::shared_ptr<ServiceManager> self);
+ ~ServiceManager();
+
ResultVal<Kernel::SharedPtr<Kernel::ServerPort>> RegisterService(std::string name,
unsigned int max_sessions);
ResultVal<Kernel::SharedPtr<Kernel::ClientPort>> GetServicePort(const std::string& name);
@@ -60,7 +61,4 @@ private:
std::unordered_map<std::string, Kernel::SharedPtr<Kernel::ClientPort>> registered_services;
};
-extern std::shared_ptr<ServiceManager> g_service_manager;
-
-} // namespace SM
-} // namespace Service
+} // namespace Service::SM
diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp
new file mode 100644
index 000000000..ab909fdaa
--- /dev/null
+++ b/src/core/hle/service/sockets/bsd.cpp
@@ -0,0 +1,114 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/ipc_helpers.h"
+#include "core/hle/service/sockets/bsd.h"
+
+namespace Service::Sockets {
+
+void BSD::RegisterClient(Kernel::HLERequestContext& ctx) {
+ NGLOG_WARNING(Service, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(0); // bsd errno
+}
+
+void BSD::StartMonitoring(Kernel::HLERequestContext& ctx) {
+ NGLOG_WARNING(Service, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(0); // bsd errno
+}
+
+void BSD::Socket(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ u32 domain = rp.Pop<u32>();
+ u32 type = rp.Pop<u32>();
+ u32 protocol = rp.Pop<u32>();
+
+ NGLOG_WARNING(Service, "(STUBBED) called domain={} type={} protocol={}", domain, type,
+ protocol);
+
+ u32 fd = next_fd++;
+
+ IPC::ResponseBuilder rb{ctx, 4};
+
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(fd);
+ rb.Push<u32>(0); // bsd errno
+}
+
+void BSD::Connect(Kernel::HLERequestContext& ctx) {
+ NGLOG_WARNING(Service, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 4};
+
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(0); // ret
+ rb.Push<u32>(0); // bsd errno
+}
+
+void BSD::SendTo(Kernel::HLERequestContext& ctx) {
+ NGLOG_WARNING(Service, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 4};
+
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(0); // ret
+ rb.Push<u32>(0); // bsd errno
+}
+
+void BSD::Close(Kernel::HLERequestContext& ctx) {
+ NGLOG_WARNING(Service, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 4};
+
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(0); // ret
+ rb.Push<u32>(0); // bsd errno
+}
+
+BSD::BSD(const char* name) : ServiceFramework(name) {
+ static const FunctionInfo functions[] = {
+ {0, &BSD::RegisterClient, "RegisterClient"},
+ {1, &BSD::StartMonitoring, "StartMonitoring"},
+ {2, &BSD::Socket, "Socket"},
+ {3, nullptr, "SocketExempt"},
+ {4, nullptr, "Open"},
+ {5, nullptr, "Select"},
+ {6, nullptr, "Poll"},
+ {7, nullptr, "Sysctl"},
+ {8, nullptr, "Recv"},
+ {9, nullptr, "RecvFrom"},
+ {10, nullptr, "Send"},
+ {11, &BSD::SendTo, "SendTo"},
+ {12, nullptr, "Accept"},
+ {13, nullptr, "Bind"},
+ {14, &BSD::Connect, "Connect"},
+ {15, nullptr, "GetPeerName"},
+ {16, nullptr, "GetSockName"},
+ {17, nullptr, "GetSockOpt"},
+ {18, nullptr, "Listen"},
+ {19, nullptr, "Ioctl"},
+ {20, nullptr, "Fcntl"},
+ {21, nullptr, "SetSockOpt"},
+ {22, nullptr, "Shutdown"},
+ {23, nullptr, "ShutdownAllSockets"},
+ {24, nullptr, "Write"},
+ {25, nullptr, "Read"},
+ {26, &BSD::Close, "Close"},
+ {27, nullptr, "DuplicateSocket"},
+ {28, nullptr, "GetResourceStatistics"},
+ {29, nullptr, "RecvMMsg"},
+ {30, nullptr, "SendMMsg"},
+ };
+ RegisterHandlers(functions);
+}
+
+} // namespace Service::Sockets
diff --git a/src/core/hle/service/sockets/bsd_u.h b/src/core/hle/service/sockets/bsd.h
index 4e1252e9d..a6b1ca7d0 100644
--- a/src/core/hle/service/sockets/bsd_u.h
+++ b/src/core/hle/service/sockets/bsd.h
@@ -7,13 +7,12 @@
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/service/service.h"
-namespace Service {
-namespace Sockets {
+namespace Service::Sockets {
-class BSD_U final : public ServiceFramework<BSD_U> {
+class BSD final : public ServiceFramework<BSD> {
public:
- BSD_U();
- ~BSD_U() = default;
+ explicit BSD(const char* name);
+ ~BSD() = default;
private:
void RegisterClient(Kernel::HLERequestContext& ctx);
@@ -27,5 +26,4 @@ private:
u32 next_fd = 1;
};
-} // namespace Sockets
-} // namespace Service
+} // namespace Service::Sockets
diff --git a/src/core/hle/service/sockets/bsd_u.cpp b/src/core/hle/service/sockets/bsd_u.cpp
deleted file mode 100644
index 2ca1000ca..000000000
--- a/src/core/hle/service/sockets/bsd_u.cpp
+++ /dev/null
@@ -1,88 +0,0 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include "core/hle/ipc_helpers.h"
-#include "core/hle/service/sockets/bsd_u.h"
-
-namespace Service {
-namespace Sockets {
-
-void BSD_U::RegisterClient(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service, "(STUBBED) called");
-
- IPC::ResponseBuilder rb{ctx, 3};
-
- rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(0); // bsd errno
-}
-
-void BSD_U::StartMonitoring(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service, "(STUBBED) called");
-
- IPC::ResponseBuilder rb{ctx, 3};
-
- rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(0); // bsd errno
-}
-
-void BSD_U::Socket(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
-
- u32 domain = rp.Pop<u32>();
- u32 type = rp.Pop<u32>();
- u32 protocol = rp.Pop<u32>();
-
- LOG_WARNING(Service, "(STUBBED) called domain=%u type=%u protocol=%u", domain, type, protocol);
-
- u32 fd = next_fd++;
-
- IPC::ResponseBuilder rb{ctx, 4};
-
- rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(fd);
- rb.Push<u32>(0); // bsd errno
-}
-
-void BSD_U::Connect(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service, "(STUBBED) called");
-
- IPC::ResponseBuilder rb{ctx, 4};
-
- rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(0); // ret
- rb.Push<u32>(0); // bsd errno
-}
-
-void BSD_U::SendTo(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service, "(STUBBED) called");
-
- IPC::ResponseBuilder rb{ctx, 4};
-
- rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(0); // ret
- rb.Push<u32>(0); // bsd errno
-}
-
-void BSD_U::Close(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service, "(STUBBED) called");
-
- IPC::ResponseBuilder rb{ctx, 4};
-
- rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(0); // ret
- rb.Push<u32>(0); // bsd errno
-}
-
-BSD_U::BSD_U() : ServiceFramework("bsd:u") {
- static const FunctionInfo functions[] = {{0, &BSD_U::RegisterClient, "RegisterClient"},
- {1, &BSD_U::StartMonitoring, "StartMonitoring"},
- {2, &BSD_U::Socket, "Socket"},
- {11, &BSD_U::SendTo, "SendTo"},
- {14, &BSD_U::Connect, "Connect"},
- {26, &BSD_U::Close, "Close"}};
- RegisterHandlers(functions);
-}
-
-} // namespace Sockets
-} // namespace Service
diff --git a/src/core/hle/service/sockets/nsd.cpp b/src/core/hle/service/sockets/nsd.cpp
new file mode 100644
index 000000000..8682dc2e0
--- /dev/null
+++ b/src/core/hle/service/sockets/nsd.cpp
@@ -0,0 +1,32 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/sockets/nsd.h"
+
+namespace Service::Sockets {
+
+NSD::NSD(const char* name) : ServiceFramework(name) {
+ static const FunctionInfo functions[] = {
+ {10, nullptr, "GetSettingName"},
+ {11, nullptr, "GetEnvironmentIdentifier"},
+ {12, nullptr, "GetDeviceId"},
+ {13, nullptr, "DeleteSettings"},
+ {14, nullptr, "ImportSettings"},
+ {20, nullptr, "Resolve"},
+ {21, nullptr, "ResolveEx"},
+ {30, nullptr, "GetNasServiceSetting"},
+ {31, nullptr, "GetNasServiceSettingEx"},
+ {40, nullptr, "GetNasRequestFqdn"},
+ {41, nullptr, "GetNasRequestFqdnEx"},
+ {42, nullptr, "GetNasApiFqdn"},
+ {43, nullptr, "GetNasApiFqdnEx"},
+ {50, nullptr, "GetCurrentSetting"},
+ {60, nullptr, "ReadSaveDataFromFsForTest"},
+ {61, nullptr, "WriteSaveDataToFsForTest"},
+ {62, nullptr, "DeleteSaveDataOfFsForTest"},
+ };
+ RegisterHandlers(functions);
+}
+
+} // namespace Service::Sockets
diff --git a/src/core/hle/service/sockets/nsd.h b/src/core/hle/service/sockets/nsd.h
new file mode 100644
index 000000000..3b7edfc43
--- /dev/null
+++ b/src/core/hle/service/sockets/nsd.h
@@ -0,0 +1,18 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/kernel/hle_ipc.h"
+#include "core/hle/service/service.h"
+
+namespace Service::Sockets {
+
+class NSD final : public ServiceFramework<NSD> {
+public:
+ explicit NSD(const char* name);
+ ~NSD() = default;
+};
+
+} // namespace Service::Sockets
diff --git a/src/core/hle/service/sockets/sfdnsres.cpp b/src/core/hle/service/sockets/sfdnsres.cpp
index 4d7bc7c3e..f377e59f2 100644
--- a/src/core/hle/service/sockets/sfdnsres.cpp
+++ b/src/core/hle/service/sockets/sfdnsres.cpp
@@ -5,13 +5,12 @@
#include "core/hle/ipc_helpers.h"
#include "core/hle/service/sockets/sfdnsres.h"
-namespace Service {
-namespace Sockets {
+namespace Service::Sockets {
void SFDNSRES::GetAddrInfo(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- LOG_WARNING(Service, "(STUBBED) called");
+ NGLOG_WARNING(Service, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
@@ -19,18 +18,20 @@ void SFDNSRES::GetAddrInfo(Kernel::HLERequestContext& ctx) {
}
SFDNSRES::SFDNSRES() : ServiceFramework("sfdnsres") {
- static const FunctionInfo functions[] = {{0, nullptr, "SetDnsAddressesPrivate"},
- {1, nullptr, "GetDnsAddressPrivate"},
- {2, nullptr, "GetHostByName"},
- {3, nullptr, "GetHostByAddr"},
- {4, nullptr, "GetHostStringError"},
- {5, nullptr, "GetGaiStringError"},
- {6, &SFDNSRES::GetAddrInfo, "GetAddrInfo"},
- {7, nullptr, "GetNameInfo"},
- {8, nullptr, "RequestCancelHandle"},
- {9, nullptr, "CancelSocketCall"}};
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "SetDnsAddressesPrivate"},
+ {1, nullptr, "GetDnsAddressPrivate"},
+ {2, nullptr, "GetHostByName"},
+ {3, nullptr, "GetHostByAddr"},
+ {4, nullptr, "GetHostStringError"},
+ {5, nullptr, "GetGaiStringError"},
+ {6, &SFDNSRES::GetAddrInfo, "GetAddrInfo"},
+ {7, nullptr, "GetNameInfo"},
+ {8, nullptr, "RequestCancelHandle"},
+ {9, nullptr, "CancelSocketCall"},
+ {11, nullptr, "ClearDnsIpServerAddressArray"},
+ };
RegisterHandlers(functions);
}
-} // namespace Sockets
-} // namespace Service
+} // namespace Service::Sockets
diff --git a/src/core/hle/service/sockets/sfdnsres.h b/src/core/hle/service/sockets/sfdnsres.h
index b726a30fd..62c7e35bf 100644
--- a/src/core/hle/service/sockets/sfdnsres.h
+++ b/src/core/hle/service/sockets/sfdnsres.h
@@ -7,17 +7,15 @@
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/service/service.h"
-namespace Service {
-namespace Sockets {
+namespace Service::Sockets {
class SFDNSRES final : public ServiceFramework<SFDNSRES> {
public:
- SFDNSRES();
+ explicit SFDNSRES();
~SFDNSRES() = default;
private:
void GetAddrInfo(Kernel::HLERequestContext& ctx);
};
-} // namespace Sockets
-} // namespace Service
+} // namespace Service::Sockets
diff --git a/src/core/hle/service/sockets/sockets.cpp b/src/core/hle/service/sockets/sockets.cpp
index f1396eaa1..05bd10d35 100644
--- a/src/core/hle/service/sockets/sockets.cpp
+++ b/src/core/hle/service/sockets/sockets.cpp
@@ -2,17 +2,19 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include "core/hle/service/sockets/bsd_u.h"
+#include "core/hle/service/sockets/bsd.h"
+#include "core/hle/service/sockets/nsd.h"
#include "core/hle/service/sockets/sfdnsres.h"
#include "core/hle/service/sockets/sockets.h"
-namespace Service {
-namespace Sockets {
+namespace Service::Sockets {
void InstallInterfaces(SM::ServiceManager& service_manager) {
- std::make_shared<BSD_U>()->InstallAsService(service_manager);
+ std::make_shared<BSD>("bsd:s")->InstallAsService(service_manager);
+ std::make_shared<BSD>("bsd:u")->InstallAsService(service_manager);
+ std::make_shared<NSD>("nsd:a")->InstallAsService(service_manager);
+ std::make_shared<NSD>("nsd:u")->InstallAsService(service_manager);
std::make_shared<SFDNSRES>()->InstallAsService(service_manager);
}
-} // namespace Sockets
-} // namespace Service
+} // namespace Service::Sockets
diff --git a/src/core/hle/service/sockets/sockets.h b/src/core/hle/service/sockets/sockets.h
index 7e89c8d2c..ca8a6a7e0 100644
--- a/src/core/hle/service/sockets/sockets.h
+++ b/src/core/hle/service/sockets/sockets.h
@@ -6,11 +6,9 @@
#include "core/hle/service/service.h"
-namespace Service {
-namespace Sockets {
+namespace Service::Sockets {
/// Registers all Sockets services with the specified service manager.
void InstallInterfaces(SM::ServiceManager& service_manager);
-} // namespace Sockets
-} // namespace Service
+} // namespace Service::Sockets
diff --git a/src/core/hle/service/spl/csrng.cpp b/src/core/hle/service/spl/csrng.cpp
new file mode 100644
index 000000000..b9e6b799d
--- /dev/null
+++ b/src/core/hle/service/spl/csrng.cpp
@@ -0,0 +1,16 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/spl/csrng.h"
+
+namespace Service::SPL {
+
+CSRNG::CSRNG(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "csrng") {
+ static const FunctionInfo functions[] = {
+ {0, &CSRNG::GetRandomBytes, "GetRandomBytes"},
+ };
+ RegisterHandlers(functions);
+}
+
+} // namespace Service::SPL
diff --git a/src/core/hle/service/spl/csrng.h b/src/core/hle/service/spl/csrng.h
new file mode 100644
index 000000000..3f849b5a7
--- /dev/null
+++ b/src/core/hle/service/spl/csrng.h
@@ -0,0 +1,16 @@
+// 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/spl/module.h"
+
+namespace Service::SPL {
+
+class CSRNG final : public Module::Interface {
+public:
+ explicit CSRNG(std::shared_ptr<Module> module);
+};
+
+} // namespace Service::SPL
diff --git a/src/core/hle/service/spl/module.cpp b/src/core/hle/service/spl/module.cpp
new file mode 100644
index 000000000..76ba97156
--- /dev/null
+++ b/src/core/hle/service/spl/module.cpp
@@ -0,0 +1,40 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <cstdlib>
+#include <vector>
+#include "common/logging/log.h"
+#include "core/hle/ipc_helpers.h"
+#include "core/hle/service/spl/csrng.h"
+#include "core/hle/service/spl/module.h"
+#include "core/hle/service/spl/spl.h"
+
+namespace Service::SPL {
+
+Module::Interface::Interface(std::shared_ptr<Module> module, const char* name)
+ : ServiceFramework(name), module(std::move(module)) {}
+
+void Module::Interface::GetRandomBytes(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ size_t size = ctx.GetWriteBufferSize();
+
+ std::vector<u8> data(size);
+ std::generate(data.begin(), data.end(), std::rand);
+
+ ctx.WriteBuffer(data);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ NGLOG_DEBUG(Service_SPL, "called");
+}
+
+void InstallInterfaces(SM::ServiceManager& service_manager) {
+ auto module = std::make_shared<Module>();
+ std::make_shared<CSRNG>(module)->InstallAsService(service_manager);
+ std::make_shared<SPL>(module)->InstallAsService(service_manager);
+}
+
+} // namespace Service::SPL
diff --git a/src/core/hle/service/spl/module.h b/src/core/hle/service/spl/module.h
new file mode 100644
index 000000000..6ab91b400
--- /dev/null
+++ b/src/core/hle/service/spl/module.h
@@ -0,0 +1,27 @@
+// 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/service.h"
+
+namespace Service::SPL {
+
+class Module final {
+public:
+ class Interface : public ServiceFramework<Interface> {
+ public:
+ Interface(std::shared_ptr<Module> module, const char* name);
+
+ void GetRandomBytes(Kernel::HLERequestContext& ctx);
+
+ protected:
+ std::shared_ptr<Module> module;
+ };
+};
+
+/// Registers all SPL services with the specified service manager.
+void InstallInterfaces(SM::ServiceManager& service_manager);
+
+} // namespace Service::SPL
diff --git a/src/core/hle/service/spl/spl.cpp b/src/core/hle/service/spl/spl.cpp
new file mode 100644
index 000000000..bb1e03342
--- /dev/null
+++ b/src/core/hle/service/spl/spl.cpp
@@ -0,0 +1,45 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/spl/spl.h"
+
+namespace Service::SPL {
+
+SPL::SPL(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "spl:") {
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "GetConfig"},
+ {1, nullptr, "UserExpMod"},
+ {2, nullptr, "GenerateAesKek"},
+ {3, nullptr, "LoadAesKey"},
+ {4, nullptr, "GenerateAesKey"},
+ {5, nullptr, "SetConfig"},
+ {7, &SPL::GetRandomBytes, "GetRandomBytes"},
+ {9, nullptr, "LoadSecureExpModKey"},
+ {10, nullptr, "SecureExpMod"},
+ {11, nullptr, "IsDevelopment"},
+ {12, nullptr, "GenerateSpecificAesKey"},
+ {13, nullptr, "DecryptPrivk"},
+ {14, nullptr, "DecryptAesKey"},
+ {15, nullptr, "DecryptAesCtr"},
+ {16, nullptr, "ComputeCmac"},
+ {17, nullptr, "LoadRsaOaepKey"},
+ {18, nullptr, "UnwrapRsaOaepWrappedTitleKey"},
+ {19, nullptr, "LoadTitleKey"},
+ {20, nullptr, "UnwrapAesWrappedTitleKey"},
+ {21, nullptr, "LockAesEngine"},
+ {22, nullptr, "UnlockAesEngine"},
+ {23, nullptr, "GetSplWaitEvent"},
+ {24, nullptr, "SetSharedData"},
+ {25, nullptr, "GetSharedData"},
+ {26, nullptr, "ImportSslRsaKey"},
+ {27, nullptr, "SecureExpModWithSslKey"},
+ {28, nullptr, "ImportEsRsaKey"},
+ {29, nullptr, "SecureExpModWithEsKey"},
+ {30, nullptr, "EncryptManuRsaKeyForImport"},
+ {31, nullptr, "GetPackage2Hash"},
+ };
+ RegisterHandlers(functions);
+}
+
+} // namespace Service::SPL
diff --git a/src/core/hle/service/spl/spl.h b/src/core/hle/service/spl/spl.h
new file mode 100644
index 000000000..69c4c1747
--- /dev/null
+++ b/src/core/hle/service/spl/spl.h
@@ -0,0 +1,16 @@
+// 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/spl/module.h"
+
+namespace Service::SPL {
+
+class SPL final : public Module::Interface {
+public:
+ explicit SPL(std::shared_ptr<Module> module);
+};
+
+} // namespace Service::SPL
diff --git a/src/core/hle/service/ssl/ssl.cpp b/src/core/hle/service/ssl/ssl.cpp
new file mode 100644
index 000000000..b3dad8b06
--- /dev/null
+++ b/src/core/hle/service/ssl/ssl.cpp
@@ -0,0 +1,119 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/ipc_helpers.h"
+#include "core/hle/service/ssl/ssl.h"
+
+namespace Service::SSL {
+
+class ISslConnection final : public ServiceFramework<ISslConnection> {
+public:
+ ISslConnection() : ServiceFramework("ISslConnection") {
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "SetSocketDescriptor"},
+ {1, nullptr, "SetHostName"},
+ {2, nullptr, "SetVerifyOption"},
+ {3, nullptr, "SetIoMode"},
+ {4, nullptr, "GetSocketDescriptor"},
+ {5, nullptr, "GetHostName"},
+ {6, nullptr, "GetVerifyOption"},
+ {7, nullptr, "GetIoMode"},
+ {8, nullptr, "DoHandshake"},
+ {9, nullptr, "DoHandshakeGetServerCert"},
+ {10, nullptr, "Read"},
+ {11, nullptr, "Write"},
+ {12, nullptr, "Pending"},
+ {13, nullptr, "Peek"},
+ {14, nullptr, "Poll"},
+ {15, nullptr, "GetVerifyCertError"},
+ {16, nullptr, "GetNeededServerCertBufferSize"},
+ {17, nullptr, "SetSessionCacheMode"},
+ {18, nullptr, "GetSessionCacheMode"},
+ {19, nullptr, "FlushSessionCache"},
+ {20, nullptr, "SetRenegotiationMode"},
+ {21, nullptr, "GetRenegotiationMode"},
+ {22, nullptr, "SetOption"},
+ {23, nullptr, "GetOption"},
+ {24, nullptr, "GetVerifyCertErrors"},
+ {25, nullptr, "GetCipherInfo"},
+ };
+ RegisterHandlers(functions);
+ }
+};
+
+class ISslContext final : public ServiceFramework<ISslContext> {
+public:
+ ISslContext() : ServiceFramework("ISslContext") {
+ static const FunctionInfo functions[] = {
+ {0, &ISslContext::SetOption, "SetOption"},
+ {1, nullptr, "GetOption"},
+ {2, &ISslContext::CreateConnection, "CreateConnection"},
+ {3, nullptr, "GetConnectionCount"},
+ {4, nullptr, "ImportServerPki"},
+ {5, nullptr, "ImportClientPki"},
+ {6, nullptr, "RemoveServerPki"},
+ {7, nullptr, "RemoveClientPki"},
+ {8, nullptr, "RegisterInternalPki"},
+ {9, nullptr, "AddPolicyOid"},
+ {10, nullptr, "ImportCrl"},
+ {11, nullptr, "RemoveCrl"},
+ };
+ RegisterHandlers(functions);
+ }
+ ~ISslContext() = default;
+
+private:
+ void SetOption(Kernel::HLERequestContext& ctx) {
+ NGLOG_WARNING(Service_SSL, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+
+ IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0);
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void CreateConnection(Kernel::HLERequestContext& ctx) {
+ NGLOG_WARNING(Service_SSL, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<ISslConnection>();
+ }
+};
+
+void SSL::CreateContext(Kernel::HLERequestContext& ctx) {
+ NGLOG_WARNING(Service_SSL, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<ISslContext>();
+}
+
+SSL::SSL() : ServiceFramework("ssl") {
+ static const FunctionInfo functions[] = {
+ {0, &SSL::CreateContext, "CreateContext"},
+ {1, nullptr, "GetContextCount"},
+ {2, nullptr, "GetCertificates"},
+ {3, nullptr, "GetCertificateBufSize"},
+ {4, nullptr, "DebugIoctl"},
+ {5, &SSL::SetInterfaceVersion, "SetInterfaceVersion"},
+ {6, nullptr, "FlushSessionCache"},
+ };
+ RegisterHandlers(functions);
+}
+
+void SSL::SetInterfaceVersion(Kernel::HLERequestContext& ctx) {
+ NGLOG_WARNING(Service_SSL, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ u32 unk1 = rp.Pop<u32>(); // Probably minor/major?
+ u32 unk2 = rp.Pop<u32>(); // TODO(ogniK): Figure out what this does
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
+void InstallInterfaces(SM::ServiceManager& service_manager) {
+ std::make_shared<SSL>()->InstallAsService(service_manager);
+}
+
+} // namespace Service::SSL
diff --git a/src/core/hle/service/ssl/ssl.h b/src/core/hle/service/ssl/ssl.h
new file mode 100644
index 000000000..8fef13022
--- /dev/null
+++ b/src/core/hle/service/ssl/ssl.h
@@ -0,0 +1,24 @@
+// 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/service.h"
+
+namespace Service::SSL {
+
+class SSL final : public ServiceFramework<SSL> {
+public:
+ explicit SSL();
+ ~SSL() = default;
+
+private:
+ void CreateContext(Kernel::HLERequestContext& ctx);
+ void SetInterfaceVersion(Kernel::HLERequestContext& ctx);
+};
+
+/// Registers all SSL services with the specified service manager.
+void InstallInterfaces(SM::ServiceManager& service_manager);
+
+} // namespace Service::SSL
diff --git a/src/core/hle/service/time/time.cpp b/src/core/hle/service/time/time.cpp
index ad49f4265..654012189 100644
--- a/src/core/hle/service/time/time.cpp
+++ b/src/core/hle/service/time/time.cpp
@@ -3,6 +3,7 @@
// Refer to the license.txt file included.
#include <chrono>
+#include <ctime>
#include "common/logging/log.h"
#include "core/core_timing.h"
#include "core/hle/ipc_helpers.h"
@@ -12,15 +13,18 @@
#include "core/hle/service/time/time_s.h"
#include "core/hle/service/time/time_u.h"
-namespace Service {
-namespace Time {
+namespace Service::Time {
class ISystemClock final : public ServiceFramework<ISystemClock> {
public:
ISystemClock() : ServiceFramework("ISystemClock") {
static const FunctionInfo functions[] = {
{0, &ISystemClock::GetCurrentTime, "GetCurrentTime"},
- {2, &ISystemClock::GetSystemClockContext, "GetSystemClockContext"}};
+ {1, nullptr, "SetCurrentTime"},
+ {2, &ISystemClock::GetSystemClockContext, "GetSystemClockContext"},
+ {3, nullptr, "SetSystemClockContext"},
+
+ };
RegisterHandlers(functions);
}
@@ -29,14 +33,14 @@ private:
const s64 time_since_epoch{std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch())
.count()};
- LOG_DEBUG(Service_Time, "called");
+ NGLOG_DEBUG(Service_Time, "called");
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
rb.Push<u64>(time_since_epoch);
}
void GetSystemClockContext(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_Time, "(STUBBED) called");
+ NGLOG_WARNING(Service_Time, "(STUBBED) called");
SystemClockContext system_clock_ontext{};
IPC::ResponseBuilder rb{ctx, (sizeof(SystemClockContext) / 4) + 2};
rb.Push(RESULT_SUCCESS);
@@ -55,8 +59,9 @@ public:
private:
void GetCurrentTimePoint(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_Time, "called");
- SteadyClockTimePoint steady_clock_time_point{cyclesToMs(CoreTiming::GetTicks()) / 1000};
+ NGLOG_DEBUG(Service_Time, "called");
+ SteadyClockTimePoint steady_clock_time_point{
+ CoreTiming::cyclesToMs(CoreTiming::GetTicks()) / 1000};
IPC::ResponseBuilder rb{ctx, (sizeof(SteadyClockTimePoint) / 4) + 2};
rb.Push(RESULT_SUCCESS);
rb.PushRaw(steady_clock_time_point);
@@ -73,7 +78,7 @@ public:
{3, nullptr, "LoadLocationNameList"},
{4, &ITimeZoneService::LoadTimeZoneRule, "LoadTimeZoneRule"},
{5, nullptr, "GetTimeZoneRuleVersion"},
- {100, nullptr, "ToCalendarTime"},
+ {100, &ITimeZoneService::ToCalendarTime, "ToCalendarTime"},
{101, &ITimeZoneService::ToCalendarTimeWithMyRule, "ToCalendarTimeWithMyRule"},
{200, nullptr, "ToPosixTime"},
{201, nullptr, "ToPosixTimeWithMyRule"},
@@ -82,75 +87,124 @@ public:
}
private:
+ LocationName location_name{"UTC"};
+ TimeZoneRule my_time_zone_rule{};
+
void GetDeviceLocationName(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_Time, "(STUBBED) called");
- LocationName location_name{};
+ NGLOG_DEBUG(Service_Time, "called");
IPC::ResponseBuilder rb{ctx, (sizeof(LocationName) / 4) + 2};
rb.Push(RESULT_SUCCESS);
rb.PushRaw(location_name);
}
void GetTotalLocationNameCount(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_Time, "(STUBBED) called");
+ NGLOG_WARNING(Service_Time, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(0);
}
void LoadTimeZoneRule(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_Time, "(STUBBED) called");
+ NGLOG_WARNING(Service_Time, "(STUBBED) called");
+
+ ctx.WriteBuffer(&my_time_zone_rule, sizeof(TimeZoneRule));
+
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
+ void ToCalendarTime(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const u64 posix_time = rp.Pop<u64>();
+
+ NGLOG_WARNING(Service_Time, "(STUBBED) called, posix_time=0x{:016X}", posix_time);
+
+ TimeZoneRule time_zone_rule{};
+ auto buffer = ctx.ReadBuffer();
+ std::memcpy(&time_zone_rule, buffer.data(), buffer.size());
+
+ CalendarTime calendar_time{2018, 1, 1, 0, 0, 0};
+ CalendarAdditionalInfo additional_info{};
+
+ PosixToCalendar(posix_time, calendar_time, additional_info, time_zone_rule);
+
+ IPC::ResponseBuilder rb{ctx, 10};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushRaw(calendar_time);
+ rb.PushRaw(additional_info);
+ }
+
void ToCalendarTimeWithMyRule(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- u64 posix_time = rp.Pop<u64>();
+ const u64 posix_time = rp.Pop<u64>();
- LOG_WARNING(Service_Time, "(STUBBED) called, posix_time=0x%016llX", posix_time);
+ NGLOG_WARNING(Service_Time, "(STUBBED) called, posix_time=0x{:016X}", posix_time);
CalendarTime calendar_time{2018, 1, 1, 0, 0, 0};
CalendarAdditionalInfo additional_info{};
+
+ PosixToCalendar(posix_time, calendar_time, additional_info, my_time_zone_rule);
+
IPC::ResponseBuilder rb{ctx, 10};
rb.Push(RESULT_SUCCESS);
rb.PushRaw(calendar_time);
rb.PushRaw(additional_info);
}
+
+ void PosixToCalendar(u64 posix_time, CalendarTime& calendar_time,
+ CalendarAdditionalInfo& additional_info, const TimeZoneRule& /*rule*/) {
+ std::time_t t(posix_time);
+ std::tm* tm = std::localtime(&t);
+ if (!tm) {
+ return;
+ }
+ calendar_time.year = tm->tm_year + 1900;
+ calendar_time.month = tm->tm_mon + 1;
+ calendar_time.day = tm->tm_mday;
+ calendar_time.hour = tm->tm_hour;
+ calendar_time.minute = tm->tm_min;
+ calendar_time.second = tm->tm_sec;
+
+ additional_info.day_of_week = tm->tm_wday;
+ additional_info.day_of_year = tm->tm_yday;
+ std::memcpy(additional_info.name.data(), "UTC", sizeof("UTC"));
+ additional_info.utc_offset = 0;
+ }
};
void Module::Interface::GetStandardUserSystemClock(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<ISystemClock>();
- LOG_DEBUG(Service_Time, "called");
+ NGLOG_DEBUG(Service_Time, "called");
}
void Module::Interface::GetStandardNetworkSystemClock(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<ISystemClock>();
- LOG_DEBUG(Service_Time, "called");
+ NGLOG_DEBUG(Service_Time, "called");
}
void Module::Interface::GetStandardSteadyClock(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<ISteadyClock>();
- LOG_DEBUG(Service_Time, "called");
+ NGLOG_DEBUG(Service_Time, "called");
}
void Module::Interface::GetTimeZoneService(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<ITimeZoneService>();
- LOG_DEBUG(Service_Time, "called");
+ NGLOG_DEBUG(Service_Time, "called");
}
void Module::Interface::GetStandardLocalSystemClock(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<ISystemClock>();
- LOG_DEBUG(Service_Time, "called");
+ NGLOG_DEBUG(Service_Time, "called");
}
Module::Interface::Interface(std::shared_ptr<Module> time, const char* name)
@@ -162,5 +216,4 @@ void InstallInterfaces(SM::ServiceManager& service_manager) {
std::make_shared<TIME_U>(time)->InstallAsService(service_manager);
}
-} // namespace Time
-} // namespace Service
+} // namespace Service::Time
diff --git a/src/core/hle/service/time/time.h b/src/core/hle/service/time/time.h
index 197029e7a..49af38589 100644
--- a/src/core/hle/service/time/time.h
+++ b/src/core/hle/service/time/time.h
@@ -4,14 +4,13 @@
#pragma once
+#include <array>
#include "core/hle/service/service.h"
-namespace Service {
-namespace Time {
+namespace Service::Time {
-// TODO(Rozelette) RE this structure
struct LocationName {
- INSERT_PADDING_BYTES(0x24);
+ std::array<u8, 0x24> name;
};
static_assert(sizeof(LocationName) == 0x24, "LocationName is incorrect size");
@@ -26,26 +25,34 @@ struct CalendarTime {
};
static_assert(sizeof(CalendarTime) == 0x8, "CalendarTime structure has incorrect size");
-// TODO(Rozelette) RE this structure
struct CalendarAdditionalInfo {
- INSERT_PADDING_BYTES(0x18);
+ u32_le day_of_week;
+ u32_le day_of_year;
+ std::array<u8, 8> name;
+ INSERT_PADDING_BYTES(1);
+ s32_le utc_offset;
};
static_assert(sizeof(CalendarAdditionalInfo) == 0x18,
"CalendarAdditionalInfo structure has incorrect size");
-// TODO(bunnei) RE this structure
-struct SystemClockContext {
- INSERT_PADDING_BYTES(0x20);
+// TODO(mailwl) RE this structure
+struct TimeZoneRule {
+ INSERT_PADDING_BYTES(0x4000);
};
-static_assert(sizeof(SystemClockContext) == 0x20,
- "SystemClockContext structure has incorrect size");
struct SteadyClockTimePoint {
- u64 value;
+ u64_le value;
INSERT_PADDING_WORDS(4);
};
static_assert(sizeof(SteadyClockTimePoint) == 0x18, "SteadyClockTimePoint is incorrect size");
+struct SystemClockContext {
+ u64_le offset;
+ SteadyClockTimePoint time_point;
+};
+static_assert(sizeof(SystemClockContext) == 0x20,
+ "SystemClockContext structure has incorrect size");
+
class Module final {
public:
class Interface : public ServiceFramework<Interface> {
@@ -66,5 +73,4 @@ public:
/// Registers all Time services with the specified service manager.
void InstallInterfaces(SM::ServiceManager& service_manager);
-} // namespace Time
-} // namespace Service
+} // namespace Service::Time
diff --git a/src/core/hle/service/time/time_s.cpp b/src/core/hle/service/time/time_s.cpp
index b172b2bd6..0b599ea00 100644
--- a/src/core/hle/service/time/time_s.cpp
+++ b/src/core/hle/service/time/time_s.cpp
@@ -4,8 +4,7 @@
#include "core/hle/service/time/time_s.h"
-namespace Service {
-namespace Time {
+namespace Service::Time {
TIME_S::TIME_S(std::shared_ptr<Module> time) : Module::Interface(std::move(time), "time:s") {
static const FunctionInfo functions[] = {
@@ -14,9 +13,19 @@ TIME_S::TIME_S(std::shared_ptr<Module> time) : Module::Interface(std::move(time)
{2, &TIME_S::GetStandardSteadyClock, "GetStandardSteadyClock"},
{3, &TIME_S::GetTimeZoneService, "GetTimeZoneService"},
{4, &TIME_S::GetStandardLocalSystemClock, "GetStandardLocalSystemClock"},
+ {5, nullptr, "GetEphemeralNetworkSystemClock"},
+ {50, nullptr, "SetStandardSteadyClockInternalOffset"},
+ {100, nullptr, "IsStandardUserSystemClockAutomaticCorrectionEnabled"},
+ {101, nullptr, "SetStandardUserSystemClockAutomaticCorrectionEnabled"},
+ {102, nullptr, "GetStandardUserSystemClockInitialYear"},
+ {200, nullptr, "IsStandardNetworkSystemClockAccuracySufficient"},
+ {300, nullptr, "CalculateMonotonicSystemClockBaseTimePoint"},
+ {400, nullptr, "GetClockSnapshot"},
+ {401, nullptr, "GetClockSnapshotFromSystemClockContext"},
+ {500, nullptr, "CalculateStandardUserSystemClockDifferenceByUser"},
+ {501, nullptr, "CalculateSpanBetween"},
};
RegisterHandlers(functions);
}
-} // namespace Time
-} // namespace Service
+} // namespace Service::Time
diff --git a/src/core/hle/service/time/time_s.h b/src/core/hle/service/time/time_s.h
index abc2a8c5a..4a2daa513 100644
--- a/src/core/hle/service/time/time_s.h
+++ b/src/core/hle/service/time/time_s.h
@@ -6,13 +6,11 @@
#include "core/hle/service/time/time.h"
-namespace Service {
-namespace Time {
+namespace Service::Time {
class TIME_S final : public Module::Interface {
public:
explicit TIME_S(std::shared_ptr<Module> time);
};
-} // namespace Time
-} // namespace Service
+} // namespace Service::Time
diff --git a/src/core/hle/service/time/time_u.cpp b/src/core/hle/service/time/time_u.cpp
index fc1ace325..1ed42c419 100644
--- a/src/core/hle/service/time/time_u.cpp
+++ b/src/core/hle/service/time/time_u.cpp
@@ -4,8 +4,7 @@
#include "core/hle/service/time/time_u.h"
-namespace Service {
-namespace Time {
+namespace Service::Time {
TIME_U::TIME_U(std::shared_ptr<Module> time) : Module::Interface(std::move(time), "time:u") {
static const FunctionInfo functions[] = {
@@ -14,9 +13,19 @@ TIME_U::TIME_U(std::shared_ptr<Module> time) : Module::Interface(std::move(time)
{2, &TIME_U::GetStandardSteadyClock, "GetStandardSteadyClock"},
{3, &TIME_U::GetTimeZoneService, "GetTimeZoneService"},
{4, &TIME_U::GetStandardLocalSystemClock, "GetStandardLocalSystemClock"},
+ {5, nullptr, "GetEphemeralNetworkSystemClock"},
+ {50, nullptr, "SetStandardSteadyClockInternalOffset"},
+ {100, nullptr, "IsStandardUserSystemClockAutomaticCorrectionEnabled"},
+ {101, nullptr, "SetStandardUserSystemClockAutomaticCorrectionEnabled"},
+ {102, nullptr, "GetStandardUserSystemClockInitialYear"},
+ {200, nullptr, "IsStandardNetworkSystemClockAccuracySufficient"},
+ {300, nullptr, "CalculateMonotonicSystemClockBaseTimePoint"},
+ {400, nullptr, "GetClockSnapshot"},
+ {401, nullptr, "GetClockSnapshotFromSystemClockContext"},
+ {500, nullptr, "CalculateStandardUserSystemClockDifferenceByUser"},
+ {501, nullptr, "CalculateSpanBetween"},
};
RegisterHandlers(functions);
}
-} // namespace Time
-} // namespace Service
+} // namespace Service::Time
diff --git a/src/core/hle/service/time/time_u.h b/src/core/hle/service/time/time_u.h
index f99d25057..3724bcdc7 100644
--- a/src/core/hle/service/time/time_u.h
+++ b/src/core/hle/service/time/time_u.h
@@ -6,13 +6,11 @@
#include "core/hle/service/time/time.h"
-namespace Service {
-namespace Time {
+namespace Service::Time {
class TIME_U final : public Module::Interface {
public:
explicit TIME_U(std::shared_ptr<Module> time);
};
-} // namespace Time
-} // namespace Service
+} // namespace Service::Time
diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp
index 0aa621dfe..e86556671 100644
--- a/src/core/hle/service/vi/vi.cpp
+++ b/src/core/hle/service/vi/vi.cpp
@@ -4,21 +4,24 @@
#include <algorithm>
#include <array>
+#include <memory>
+#include <boost/optional.hpp>
#include "common/alignment.h"
#include "common/scope_exit.h"
#include "core/core_timing.h"
#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/event.h"
#include "core/hle/service/nvdrv/nvdrv.h"
#include "core/hle/service/nvflinger/buffer_queue.h"
#include "core/hle/service/vi/vi.h"
#include "core/hle/service/vi/vi_m.h"
#include "core/hle/service/vi/vi_s.h"
#include "core/hle/service/vi/vi_u.h"
+#include "core/settings.h"
#include "video_core/renderer_base.h"
#include "video_core/video_core.h"
-namespace Service {
-namespace VI {
+namespace Service::VI {
struct DisplayInfo {
char display_name[0x40]{"Default"};
@@ -146,7 +149,7 @@ private:
class NativeWindow : public Parcel {
public:
- explicit NativeWindow(u32 id) : Parcel() {
+ explicit NativeWindow(u32 id) {
data.id = id;
}
~NativeWindow() override = default;
@@ -193,7 +196,7 @@ public:
class IGBPConnectResponseParcel : public Parcel {
public:
- explicit IGBPConnectResponseParcel(u32 width, u32 height) : Parcel() {
+ explicit IGBPConnectResponseParcel(u32 width, u32 height) {
data.width = width;
data.height = height;
}
@@ -243,10 +246,6 @@ public:
};
class IGBPSetPreallocatedBufferResponseParcel : public Parcel {
-public:
- IGBPSetPreallocatedBufferResponseParcel() : Parcel() {}
- ~IGBPSetPreallocatedBufferResponseParcel() override = default;
-
protected:
void SerializeData() override {
// TODO(Subv): Find out what this means
@@ -285,7 +284,7 @@ static_assert(sizeof(BufferProducerFence) == 36, "BufferProducerFence has wrong
class IGBPDequeueBufferResponseParcel : public Parcel {
public:
- explicit IGBPDequeueBufferResponseParcel(u32 slot) : Parcel(), slot(slot) {}
+ explicit IGBPDequeueBufferResponseParcel(u32 slot) : slot(slot) {}
~IGBPDequeueBufferResponseParcel() override = default;
protected:
@@ -379,7 +378,7 @@ public:
class IGBPQueueBufferResponseParcel : public Parcel {
public:
- explicit IGBPQueueBufferResponseParcel(u32 width, u32 height) : Parcel() {
+ explicit IGBPQueueBufferResponseParcel(u32 width, u32 height) {
data.width = width;
data.height = height;
}
@@ -420,7 +419,7 @@ public:
class IGBPQueryResponseParcel : public Parcel {
public:
- explicit IGBPQueryResponseParcel(u32 value) : Parcel(), value(value) {}
+ explicit IGBPQueryResponseParcel(u32 value) : value(value) {}
~IGBPQueryResponseParcel() override = default;
protected:
@@ -471,7 +470,7 @@ private:
u32 flags = rp.Pop<u32>();
auto buffer_queue = nv_flinger->GetBufferQueue(id);
- LOG_DEBUG(Service_VI, "called, transaction=%x", transaction);
+ NGLOG_DEBUG(Service_VI, "called, transaction={:X}", static_cast<u32>(transaction));
if (transaction == TransactionId::Connect) {
IGBPConnectRequestParcel request{ctx.ReadBuffer()};
@@ -486,12 +485,30 @@ private:
ctx.WriteBuffer(response.Serialize());
} else if (transaction == TransactionId::DequeueBuffer) {
IGBPDequeueBufferRequestParcel request{ctx.ReadBuffer()};
-
- u32 slot = buffer_queue->DequeueBuffer(request.data.pixel_format, request.data.width,
- request.data.height);
-
- IGBPDequeueBufferResponseParcel response{slot};
- ctx.WriteBuffer(response.Serialize());
+ const u32 width{request.data.width};
+ const u32 height{request.data.height};
+ boost::optional<u32> slot = buffer_queue->DequeueBuffer(width, height);
+
+ if (slot != boost::none) {
+ // Buffer is available
+ IGBPDequeueBufferResponseParcel response{*slot};
+ ctx.WriteBuffer(response.Serialize());
+ } else {
+ // Wait the current thread until a buffer becomes available
+ auto wait_event = ctx.SleepClientThread(
+ Kernel::GetCurrentThread(), "IHOSBinderDriver::DequeueBuffer", -1,
+ [=](Kernel::SharedPtr<Kernel::Thread> thread, Kernel::HLERequestContext& ctx,
+ ThreadWakeupReason reason) {
+ // Repeat TransactParcel DequeueBuffer when a buffer is available
+ auto buffer_queue = nv_flinger->GetBufferQueue(id);
+ boost::optional<u32> slot = buffer_queue->DequeueBuffer(width, height);
+ IGBPDequeueBufferResponseParcel response{*slot};
+ ctx.WriteBuffer(response.Serialize());
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ });
+ buffer_queue->SetBufferWaitEvent(std::move(wait_event));
+ }
} else if (transaction == TransactionId::RequestBuffer) {
IGBPRequestBufferRequestParcel request{ctx.ReadBuffer()};
@@ -515,7 +532,7 @@ private:
IGBPQueryResponseParcel response{value};
ctx.WriteBuffer(response.Serialize());
} else if (transaction == TransactionId::CancelBuffer) {
- LOG_WARNING(Service_VI, "(STUBBED) called, transaction=CancelBuffer");
+ NGLOG_WARNING(Service_VI, "(STUBBED) called, transaction=CancelBuffer");
} else {
ASSERT_MSG(false, "Unimplemented");
}
@@ -530,7 +547,8 @@ private:
s32 addval = rp.PopRaw<s32>();
u32 type = rp.Pop<u32>();
- LOG_WARNING(Service_VI, "(STUBBED) called id=%u, addval=%08X, type=%08X", id, addval, type);
+ NGLOG_WARNING(Service_VI, "(STUBBED) called id={}, addval={:08X}, type={:08X}", id, addval,
+ type);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
@@ -544,7 +562,7 @@ private:
// TODO(Subv): Find out what this actually is.
- LOG_WARNING(Service_VI, "(STUBBED) called id=%u, unknown=%08X", id, unknown);
+ NGLOG_WARNING(Service_VI, "(STUBBED) called id={}, unknown={:08X}", id, unknown);
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
rb.PushCopyObjects(buffer_queue->GetNativeHandle());
@@ -558,7 +576,48 @@ public:
ISystemDisplayService() : ServiceFramework("ISystemDisplayService") {
static const FunctionInfo functions[] = {
{1200, nullptr, "GetZOrderCountMin"},
+ {1202, nullptr, "GetZOrderCountMax"},
+ {1203, nullptr, "GetDisplayLogicalResolution"},
+ {1204, nullptr, "SetDisplayMagnification"},
+ {2201, nullptr, "SetLayerPosition"},
+ {2203, nullptr, "SetLayerSize"},
+ {2204, nullptr, "GetLayerZ"},
{2205, &ISystemDisplayService::SetLayerZ, "SetLayerZ"},
+ {2207, &ISystemDisplayService::SetLayerVisibility, "SetLayerVisibility"},
+ {2209, nullptr, "SetLayerAlpha"},
+ {2312, nullptr, "CreateStrayLayer"},
+ {2400, nullptr, "OpenIndirectLayer"},
+ {2401, nullptr, "CloseIndirectLayer"},
+ {2402, nullptr, "FlipIndirectLayer"},
+ {3000, nullptr, "ListDisplayModes"},
+ {3001, nullptr, "ListDisplayRgbRanges"},
+ {3002, nullptr, "ListDisplayContentTypes"},
+ {3200, nullptr, "GetDisplayMode"},
+ {3201, nullptr, "SetDisplayMode"},
+ {3202, nullptr, "GetDisplayUnderscan"},
+ {3203, nullptr, "SetDisplayUnderscan"},
+ {3204, nullptr, "GetDisplayContentType"},
+ {3205, nullptr, "SetDisplayContentType"},
+ {3206, nullptr, "GetDisplayRgbRange"},
+ {3207, nullptr, "SetDisplayRgbRange"},
+ {3208, nullptr, "GetDisplayCmuMode"},
+ {3209, nullptr, "SetDisplayCmuMode"},
+ {3210, nullptr, "GetDisplayContrastRatio"},
+ {3211, nullptr, "SetDisplayContrastRatio"},
+ {3214, nullptr, "GetDisplayGamma"},
+ {3215, nullptr, "SetDisplayGamma"},
+ {3216, nullptr, "GetDisplayCmuLuma"},
+ {3217, nullptr, "SetDisplayCmuLuma"},
+ {8225, nullptr, "GetSharedBufferMemoryHandleId"},
+ {8250, nullptr, "OpenSharedLayer"},
+ {8251, nullptr, "CloseSharedLayer"},
+ {8252, nullptr, "ConnectSharedLayer"},
+ {8253, nullptr, "DisconnectSharedLayer"},
+ {8254, nullptr, "AcquireSharedFrameBuffer"},
+ {8255, nullptr, "PresentSharedFrameBuffer"},
+ {8256, nullptr, "GetSharedFrameBufferAcquirableEvent"},
+ {8257, nullptr, "FillSharedFrameBufferColor"},
+ {8258, nullptr, "CancelSharedFrameBuffer"},
};
RegisterHandlers(functions);
}
@@ -566,7 +625,7 @@ public:
private:
void SetLayerZ(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_VI, "(STUBBED) called");
+ NGLOG_WARNING(Service_VI, "(STUBBED) called");
IPC::RequestParser rp{ctx};
u64 layer_id = rp.Pop<u64>();
u64 z_value = rp.Pop<u64>();
@@ -574,6 +633,16 @@ private:
IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0);
rb.Push(RESULT_SUCCESS);
}
+
+ void SetLayerVisibility(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ u64 layer_id = rp.Pop<u64>();
+ bool visibility = rp.Pop<bool>();
+ IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0);
+ rb.Push(RESULT_SUCCESS);
+ NGLOG_WARNING(Service_VI, "(STUBBED) called, layer_id=0x{:08X}, visibility={}", layer_id,
+ visibility);
+ }
};
class IManagerDisplayService final : public ServiceFramework<IManagerDisplayService> {
@@ -581,10 +650,72 @@ public:
explicit IManagerDisplayService(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger)
: ServiceFramework("IManagerDisplayService"), nv_flinger(std::move(nv_flinger)) {
static const FunctionInfo functions[] = {
+ {200, nullptr, "AllocateProcessHeapBlock"},
+ {201, nullptr, "FreeProcessHeapBlock"},
{1020, &IManagerDisplayService::CloseDisplay, "CloseDisplay"},
{1102, nullptr, "GetDisplayResolution"},
{2010, &IManagerDisplayService::CreateManagedLayer, "CreateManagedLayer"},
+ {2011, nullptr, "DestroyManagedLayer"},
+ {2050, nullptr, "CreateIndirectLayer"},
+ {2051, nullptr, "DestroyIndirectLayer"},
+ {2052, nullptr, "CreateIndirectProducerEndPoint"},
+ {2053, nullptr, "DestroyIndirectProducerEndPoint"},
+ {2054, nullptr, "CreateIndirectConsumerEndPoint"},
+ {2055, nullptr, "DestroyIndirectConsumerEndPoint"},
+ {2300, nullptr, "AcquireLayerTexturePresentingEvent"},
+ {2301, nullptr, "ReleaseLayerTexturePresentingEvent"},
+ {2302, nullptr, "GetDisplayHotplugEvent"},
+ {2402, nullptr, "GetDisplayHotplugState"},
+ {2501, nullptr, "GetCompositorErrorInfo"},
+ {2601, nullptr, "GetDisplayErrorEvent"},
+ {4201, nullptr, "SetDisplayAlpha"},
+ {4203, nullptr, "SetDisplayLayerStack"},
+ {4205, nullptr, "SetDisplayPowerState"},
+ {4206, nullptr, "SetDefaultDisplay"},
{6000, &IManagerDisplayService::AddToLayerStack, "AddToLayerStack"},
+ {6001, nullptr, "RemoveFromLayerStack"},
+ {6002, &IManagerDisplayService::SetLayerVisibility, "SetLayerVisibility"},
+ {6003, nullptr, "SetLayerConfig"},
+ {6004, nullptr, "AttachLayerPresentationTracer"},
+ {6005, nullptr, "DetachLayerPresentationTracer"},
+ {6006, nullptr, "StartLayerPresentationRecording"},
+ {6007, nullptr, "StopLayerPresentationRecording"},
+ {6008, nullptr, "StartLayerPresentationFenceWait"},
+ {6009, nullptr, "StopLayerPresentationFenceWait"},
+ {6010, nullptr, "GetLayerPresentationAllFencesExpiredEvent"},
+ {7000, nullptr, "SetContentVisibility"},
+ {8000, nullptr, "SetConductorLayer"},
+ {8100, nullptr, "SetIndirectProducerFlipOffset"},
+ {8200, nullptr, "CreateSharedBufferStaticStorage"},
+ {8201, nullptr, "CreateSharedBufferTransferMemory"},
+ {8202, nullptr, "DestroySharedBuffer"},
+ {8203, nullptr, "BindSharedLowLevelLayerToManagedLayer"},
+ {8204, nullptr, "BindSharedLowLevelLayerToIndirectLayer"},
+ {8207, nullptr, "UnbindSharedLowLevelLayer"},
+ {8208, nullptr, "ConnectSharedLowLevelLayerToSharedBuffer"},
+ {8209, nullptr, "DisconnectSharedLowLevelLayerFromSharedBuffer"},
+ {8210, nullptr, "CreateSharedLayer"},
+ {8211, nullptr, "DestroySharedLayer"},
+ {8216, nullptr, "AttachSharedLayerToLowLevelLayer"},
+ {8217, nullptr, "ForceDetachSharedLayerFromLowLevelLayer"},
+ {8218, nullptr, "StartDetachSharedLayerFromLowLevelLayer"},
+ {8219, nullptr, "FinishDetachSharedLayerFromLowLevelLayer"},
+ {8220, nullptr, "GetSharedLayerDetachReadyEvent"},
+ {8221, nullptr, "GetSharedLowLevelLayerSynchronizedEvent"},
+ {8222, nullptr, "CheckSharedLowLevelLayerSynchronized"},
+ {8223, nullptr, "RegisterSharedBufferImporterAruid"},
+ {8224, nullptr, "UnregisterSharedBufferImporterAruid"},
+ {8227, nullptr, "CreateSharedBufferProcessHeap"},
+ {8228, nullptr, "GetSharedLayerLayerStacks"},
+ {8229, nullptr, "SetSharedLayerLayerStacks"},
+ {8291, nullptr, "PresentDetachedSharedFrameBufferToLowLevelLayer"},
+ {8292, nullptr, "FillDetachedSharedFrameBufferColor"},
+ {8293, nullptr, "GetDetachedSharedFrameBufferImage"},
+ {8294, nullptr, "SetDetachedSharedFrameBufferImage"},
+ {8295, nullptr, "CopyDetachedSharedFrameBufferImage"},
+ {8296, nullptr, "SetDetachedSharedFrameBufferSubImage"},
+ {8297, nullptr, "GetSharedFrameBufferContentParameter"},
+ {8298, nullptr, "ExpandStartupLogoOnSharedFrameBuffer"},
};
RegisterHandlers(functions);
}
@@ -592,7 +723,7 @@ public:
private:
void CloseDisplay(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_VI, "(STUBBED) called");
+ NGLOG_WARNING(Service_VI, "(STUBBED) called");
IPC::RequestParser rp{ctx};
u64 display = rp.Pop<u64>();
@@ -601,7 +732,7 @@ private:
}
void CreateManagedLayer(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_VI, "(STUBBED) called");
+ NGLOG_WARNING(Service_VI, "(STUBBED) called");
IPC::RequestParser rp{ctx};
u32 unknown = rp.Pop<u32>();
rp.Skip(1, false);
@@ -616,7 +747,7 @@ private:
}
void AddToLayerStack(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_VI, "(STUBBED) called");
+ NGLOG_WARNING(Service_VI, "(STUBBED) called");
IPC::RequestParser rp{ctx};
u32 stack = rp.Pop<u32>();
u64 layer_id = rp.Pop<u64>();
@@ -625,147 +756,182 @@ private:
rb.Push(RESULT_SUCCESS);
}
+ void SetLayerVisibility(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ u64 layer_id = rp.Pop<u64>();
+ bool visibility = rp.Pop<bool>();
+ IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0);
+ rb.Push(RESULT_SUCCESS);
+ NGLOG_WARNING(Service_VI, "(STUBBED) called, layer_id=0x{:X}, visibility={}", layer_id,
+ visibility);
+ }
+
std::shared_ptr<NVFlinger::NVFlinger> nv_flinger;
};
-void IApplicationDisplayService::GetRelayService(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_VI, "(STUBBED) called");
+class IApplicationDisplayService final : public ServiceFramework<IApplicationDisplayService> {
+public:
+ IApplicationDisplayService(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger);
+ ~IApplicationDisplayService() = default;
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IHOSBinderDriver>(nv_flinger);
-}
+private:
+ void GetRelayService(Kernel::HLERequestContext& ctx) {
+ NGLOG_WARNING(Service_VI, "(STUBBED) called");
-void IApplicationDisplayService::GetSystemDisplayService(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_VI, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IHOSBinderDriver>(nv_flinger);
+ }
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<ISystemDisplayService>();
-}
+ void GetSystemDisplayService(Kernel::HLERequestContext& ctx) {
+ NGLOG_WARNING(Service_VI, "(STUBBED) called");
-void IApplicationDisplayService::GetManagerDisplayService(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_VI, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<ISystemDisplayService>();
+ }
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IManagerDisplayService>(nv_flinger);
-}
+ void GetManagerDisplayService(Kernel::HLERequestContext& ctx) {
+ NGLOG_WARNING(Service_VI, "(STUBBED) called");
-void IApplicationDisplayService::GetIndirectDisplayTransactionService(
- Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_VI, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IManagerDisplayService>(nv_flinger);
+ }
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IHOSBinderDriver>(nv_flinger);
-}
+ void GetIndirectDisplayTransactionService(Kernel::HLERequestContext& ctx) {
+ NGLOG_WARNING(Service_VI, "(STUBBED) called");
-void IApplicationDisplayService::OpenDisplay(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_VI, "(STUBBED) called");
- IPC::RequestParser rp{ctx};
- auto name_buf = rp.PopRaw<std::array<u8, 0x40>>();
- auto end = std::find(name_buf.begin(), name_buf.end(), '\0');
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IHOSBinderDriver>(nv_flinger);
+ }
- std::string name(name_buf.begin(), end);
+ void OpenDisplay(Kernel::HLERequestContext& ctx) {
+ NGLOG_WARNING(Service_VI, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ auto name_buf = rp.PopRaw<std::array<u8, 0x40>>();
+ auto end = std::find(name_buf.begin(), name_buf.end(), '\0');
- ASSERT_MSG(name == "Default", "Non-default displays aren't supported yet");
+ std::string name(name_buf.begin(), end);
- IPC::ResponseBuilder rb = rp.MakeBuilder(4, 0, 0);
- rb.Push(RESULT_SUCCESS);
- rb.Push<u64>(nv_flinger->OpenDisplay(name));
-}
+ ASSERT_MSG(name == "Default", "Non-default displays aren't supported yet");
-void IApplicationDisplayService::CloseDisplay(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_VI, "(STUBBED) called");
- IPC::RequestParser rp{ctx};
- u64 display_id = rp.Pop<u64>();
+ IPC::ResponseBuilder rb = rp.MakeBuilder(4, 0, 0);
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u64>(nv_flinger->OpenDisplay(name));
+ }
- IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0);
- rb.Push(RESULT_SUCCESS);
-}
+ void CloseDisplay(Kernel::HLERequestContext& ctx) {
+ NGLOG_WARNING(Service_VI, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ u64 display_id = rp.Pop<u64>();
-void IApplicationDisplayService::OpenLayer(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_VI, "called");
- IPC::RequestParser rp{ctx};
- auto name_buf = rp.PopRaw<std::array<u8, 0x40>>();
- auto end = std::find(name_buf.begin(), name_buf.end(), '\0');
+ IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0);
+ rb.Push(RESULT_SUCCESS);
+ }
- std::string display_name(name_buf.begin(), end);
+ void GetDisplayResolution(Kernel::HLERequestContext& ctx) {
+ NGLOG_WARNING(Service_VI, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ u64 display_id = rp.Pop<u64>();
- u64 layer_id = rp.Pop<u64>();
- u64 aruid = rp.Pop<u64>();
+ IPC::ResponseBuilder rb = rp.MakeBuilder(6, 0, 0);
+ rb.Push(RESULT_SUCCESS);
- u64 display_id = nv_flinger->OpenDisplay(display_name);
- u32 buffer_queue_id = nv_flinger->GetBufferQueueId(display_id, layer_id);
+ if (Settings::values.use_docked_mode) {
+ rb.Push(static_cast<u64>(DisplayResolution::DockedWidth));
+ rb.Push(static_cast<u64>(DisplayResolution::DockedHeight));
+ } else {
+ rb.Push(static_cast<u64>(DisplayResolution::UndockedWidth));
+ rb.Push(static_cast<u64>(DisplayResolution::UndockedHeight));
+ }
+ }
- NativeWindow native_window{buffer_queue_id};
- IPC::ResponseBuilder rb = rp.MakeBuilder(4, 0, 0);
- rb.Push(RESULT_SUCCESS);
- rb.Push<u64>(ctx.WriteBuffer(native_window.Serialize()));
-}
+ void SetLayerScalingMode(Kernel::HLERequestContext& ctx) {
+ NGLOG_WARNING(Service_VI, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ u32 scaling_mode = rp.Pop<u32>();
+ u64 unknown = rp.Pop<u64>();
-void IApplicationDisplayService::CreateStrayLayer(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_VI, "called");
+ IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0);
+ rb.Push(RESULT_SUCCESS);
+ }
- IPC::RequestParser rp{ctx};
- u32 flags = rp.Pop<u32>();
- rp.Pop<u32>(); // padding
- u64 display_id = rp.Pop<u64>();
+ void ListDisplays(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ DisplayInfo display_info;
+ ctx.WriteBuffer(&display_info, sizeof(DisplayInfo));
+ IPC::ResponseBuilder rb = rp.MakeBuilder(4, 0, 0);
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u64>(1);
+ NGLOG_WARNING(Service_VI, "(STUBBED) called");
+ }
- // TODO(Subv): What's the difference between a Stray and a Managed layer?
+ void OpenLayer(Kernel::HLERequestContext& ctx) {
+ NGLOG_DEBUG(Service_VI, "called");
+ IPC::RequestParser rp{ctx};
+ auto name_buf = rp.PopRaw<std::array<u8, 0x40>>();
+ auto end = std::find(name_buf.begin(), name_buf.end(), '\0');
- u64 layer_id = nv_flinger->CreateLayer(display_id);
- u32 buffer_queue_id = nv_flinger->GetBufferQueueId(display_id, layer_id);
+ std::string display_name(name_buf.begin(), end);
- NativeWindow native_window{buffer_queue_id};
- IPC::ResponseBuilder rb = rp.MakeBuilder(6, 0, 0);
- rb.Push(RESULT_SUCCESS);
- rb.Push(layer_id);
- rb.Push<u64>(ctx.WriteBuffer(native_window.Serialize()));
-}
+ u64 layer_id = rp.Pop<u64>();
+ u64 aruid = rp.Pop<u64>();
-void IApplicationDisplayService::DestroyStrayLayer(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_VI, "(STUBBED) called");
+ u64 display_id = nv_flinger->OpenDisplay(display_name);
+ u32 buffer_queue_id = nv_flinger->GetBufferQueueId(display_id, layer_id);
- IPC::RequestParser rp{ctx};
- u64 layer_id = rp.Pop<u64>();
+ NativeWindow native_window{buffer_queue_id};
+ IPC::ResponseBuilder rb = rp.MakeBuilder(4, 0, 0);
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u64>(ctx.WriteBuffer(native_window.Serialize()));
+ }
- IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0);
- rb.Push(RESULT_SUCCESS);
-}
+ void CreateStrayLayer(Kernel::HLERequestContext& ctx) {
+ NGLOG_DEBUG(Service_VI, "called");
-void IApplicationDisplayService::SetLayerScalingMode(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_VI, "(STUBBED) called");
- IPC::RequestParser rp{ctx};
- u32 scaling_mode = rp.Pop<u32>();
- u64 unknown = rp.Pop<u64>();
+ IPC::RequestParser rp{ctx};
+ u32 flags = rp.Pop<u32>();
+ rp.Pop<u32>(); // padding
+ u64 display_id = rp.Pop<u64>();
- IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0);
- rb.Push(RESULT_SUCCESS);
-}
+ // TODO(Subv): What's the difference between a Stray and a Managed layer?
-void IApplicationDisplayService::ListDisplays(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- DisplayInfo display_info;
- ctx.WriteBuffer(&display_info, sizeof(DisplayInfo));
- IPC::ResponseBuilder rb = rp.MakeBuilder(4, 0, 0);
- rb.Push(RESULT_SUCCESS);
- rb.Push<u64>(1);
- LOG_WARNING(Service_VI, "(STUBBED) called");
-}
+ u64 layer_id = nv_flinger->CreateLayer(display_id);
+ u32 buffer_queue_id = nv_flinger->GetBufferQueueId(display_id, layer_id);
-void IApplicationDisplayService::GetDisplayVsyncEvent(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_VI, "(STUBBED) called");
- IPC::RequestParser rp{ctx};
- u64 display_id = rp.Pop<u64>();
+ NativeWindow native_window{buffer_queue_id};
+ IPC::ResponseBuilder rb = rp.MakeBuilder(6, 0, 0);
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(layer_id);
+ rb.Push<u64>(ctx.WriteBuffer(native_window.Serialize()));
+ }
- auto vsync_event = nv_flinger->GetVsyncEvent(display_id);
+ void DestroyStrayLayer(Kernel::HLERequestContext& ctx) {
+ NGLOG_WARNING(Service_VI, "(STUBBED) called");
- IPC::ResponseBuilder rb = rp.MakeBuilder(2, 1, 0);
- rb.Push(RESULT_SUCCESS);
- rb.PushCopyObjects(vsync_event);
-}
+ IPC::RequestParser rp{ctx};
+ u64 layer_id = rp.Pop<u64>();
+
+ IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0);
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void GetDisplayVsyncEvent(Kernel::HLERequestContext& ctx) {
+ NGLOG_WARNING(Service_VI, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ u64 display_id = rp.Pop<u64>();
+
+ auto vsync_event = nv_flinger->GetVsyncEvent(display_id);
+
+ IPC::ResponseBuilder rb = rp.MakeBuilder(2, 1, 0);
+ rb.Push(RESULT_SUCCESS);
+ rb.PushCopyObjects(vsync_event);
+ }
+
+ std::shared_ptr<NVFlinger::NVFlinger> nv_flinger;
+};
IApplicationDisplayService::IApplicationDisplayService(
std::shared_ptr<NVFlinger::NVFlinger> nv_flinger)
@@ -778,22 +944,43 @@ IApplicationDisplayService::IApplicationDisplayService(
"GetIndirectDisplayTransactionService"},
{1000, &IApplicationDisplayService::ListDisplays, "ListDisplays"},
{1010, &IApplicationDisplayService::OpenDisplay, "OpenDisplay"},
+ {1011, nullptr, "OpenDefaultDisplay"},
{1020, &IApplicationDisplayService::CloseDisplay, "CloseDisplay"},
- {2101, &IApplicationDisplayService::SetLayerScalingMode, "SetLayerScalingMode"},
+ {1101, nullptr, "SetDisplayEnabled"},
+ {1102, &IApplicationDisplayService::GetDisplayResolution, "GetDisplayResolution"},
{2020, &IApplicationDisplayService::OpenLayer, "OpenLayer"},
+ {2021, nullptr, "CloseLayer"},
{2030, &IApplicationDisplayService::CreateStrayLayer, "CreateStrayLayer"},
{2031, &IApplicationDisplayService::DestroyStrayLayer, "DestroyStrayLayer"},
+ {2101, &IApplicationDisplayService::SetLayerScalingMode, "SetLayerScalingMode"},
+ {2102, nullptr, "ConvertScalingMode"},
+ {2450, nullptr, "GetIndirectLayerImageMap"},
+ {2451, nullptr, "GetIndirectLayerImageCropMap"},
+ {2460, nullptr, "GetIndirectLayerImageRequiredMemoryInfo"},
{5202, &IApplicationDisplayService::GetDisplayVsyncEvent, "GetDisplayVsyncEvent"},
+ {5203, nullptr, "GetDisplayVsyncEventForDebug"},
};
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)) {}
+
+void Module::Interface::GetDisplayService(Kernel::HLERequestContext& ctx) {
+ NGLOG_WARNING(Service_VI, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IApplicationDisplayService>(nv_flinger);
+}
+
void InstallInterfaces(SM::ServiceManager& service_manager,
std::shared_ptr<NVFlinger::NVFlinger> nv_flinger) {
- std::make_shared<VI_M>(nv_flinger)->InstallAsService(service_manager);
- std::make_shared<VI_S>(nv_flinger)->InstallAsService(service_manager);
- std::make_shared<VI_U>(nv_flinger)->InstallAsService(service_manager);
+ 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);
}
-} // namespace VI
-} // namespace Service
+} // namespace Service::VI
diff --git a/src/core/hle/service/vi/vi.h b/src/core/hle/service/vi/vi.h
index f6be7d1e6..e8bda01d7 100644
--- a/src/core/hle/service/vi/vi.h
+++ b/src/core/hle/service/vi/vi.h
@@ -4,9 +4,6 @@
#pragma once
-#include <memory>
-#include <boost/optional.hpp>
-#include "core/hle/kernel/event.h"
#include "core/hle/service/nvflinger/nvflinger.h"
#include "core/hle/service/service.h"
@@ -14,34 +11,32 @@ namespace CoreTiming {
struct EventType;
}
-namespace Service {
-namespace VI {
+namespace Service::VI {
-class IApplicationDisplayService final : public ServiceFramework<IApplicationDisplayService> {
+enum class DisplayResolution : u32 {
+ DockedWidth = 1920,
+ DockedHeight = 1080,
+ UndockedWidth = 1280,
+ UndockedHeight = 720,
+};
+
+class Module final {
public:
- IApplicationDisplayService(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger);
- ~IApplicationDisplayService() = default;
-
-private:
- void GetRelayService(Kernel::HLERequestContext& ctx);
- void GetSystemDisplayService(Kernel::HLERequestContext& ctx);
- void GetManagerDisplayService(Kernel::HLERequestContext& ctx);
- void GetIndirectDisplayTransactionService(Kernel::HLERequestContext& ctx);
- void OpenDisplay(Kernel::HLERequestContext& ctx);
- void CloseDisplay(Kernel::HLERequestContext& ctx);
- void SetLayerScalingMode(Kernel::HLERequestContext& ctx);
- void ListDisplays(Kernel::HLERequestContext& ctx);
- void OpenLayer(Kernel::HLERequestContext& ctx);
- void CreateStrayLayer(Kernel::HLERequestContext& ctx);
- void DestroyStrayLayer(Kernel::HLERequestContext& ctx);
- void GetDisplayVsyncEvent(Kernel::HLERequestContext& ctx);
-
- std::shared_ptr<NVFlinger::NVFlinger> nv_flinger;
+ class Interface : public ServiceFramework<Interface> {
+ public:
+ Interface(std::shared_ptr<Module> module, const char* name,
+ std::shared_ptr<NVFlinger::NVFlinger> nv_flinger);
+
+ void GetDisplayService(Kernel::HLERequestContext& ctx);
+
+ protected:
+ std::shared_ptr<Module> module;
+ std::shared_ptr<NVFlinger::NVFlinger> nv_flinger;
+ };
};
/// Registers all VI services with the specified service manager.
void InstallInterfaces(SM::ServiceManager& service_manager,
std::shared_ptr<NVFlinger::NVFlinger> nv_flinger);
-} // namespace VI
-} // namespace Service
+} // namespace Service::VI
diff --git a/src/core/hle/service/vi/vi_m.cpp b/src/core/hle/service/vi/vi_m.cpp
index 5d99647dc..d47da565b 100644
--- a/src/core/hle/service/vi/vi_m.cpp
+++ b/src/core/hle/service/vi/vi_m.cpp
@@ -2,24 +2,12 @@
// 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/vi/vi.h"
#include "core/hle/service/vi/vi_m.h"
-namespace Service {
-namespace VI {
+namespace Service::VI {
-void VI_M::GetDisplayService(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_VI, "(STUBBED) called");
-
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IApplicationDisplayService>(nv_flinger);
-}
-
-VI_M::VI_M(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger)
- : ServiceFramework("vi:m"), nv_flinger(std::move(nv_flinger)) {
+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)) {
static const FunctionInfo functions[] = {
{2, &VI_M::GetDisplayService, "GetDisplayService"},
{3, nullptr, "GetDisplayServiceWithProxyNameExchange"},
@@ -27,5 +15,4 @@ VI_M::VI_M(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger)
RegisterHandlers(functions);
}
-} // namespace VI
-} // namespace Service
+} // namespace Service::VI
diff --git a/src/core/hle/service/vi/vi_m.h b/src/core/hle/service/vi/vi_m.h
index e5319b1e7..6abb9b3a3 100644
--- a/src/core/hle/service/vi/vi_m.h
+++ b/src/core/hle/service/vi/vi_m.h
@@ -4,26 +4,13 @@
#pragma once
-#include <memory>
-#include "core/hle/service/service.h"
+#include "core/hle/service/vi/vi.h"
-namespace Service {
-namespace NVFlinger {
-class NVFlinger;
-}
+namespace Service::VI {
-namespace VI {
-
-class VI_M final : public ServiceFramework<VI_M> {
+class VI_M final : public Module::Interface {
public:
- VI_M(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger);
- ~VI_M() = default;
-
-private:
- void GetDisplayService(Kernel::HLERequestContext& ctx);
-
- std::shared_ptr<NVFlinger::NVFlinger> nv_flinger;
+ explicit VI_M(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger);
};
-} // namespace VI
-} // namespace Service
+} // namespace Service::VI
diff --git a/src/core/hle/service/vi/vi_s.cpp b/src/core/hle/service/vi/vi_s.cpp
index 411757981..8f82e797f 100644
--- a/src/core/hle/service/vi/vi_s.cpp
+++ b/src/core/hle/service/vi/vi_s.cpp
@@ -2,24 +2,12 @@
// 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/vi/vi.h"
#include "core/hle/service/vi/vi_s.h"
-namespace Service {
-namespace VI {
+namespace Service::VI {
-void VI_S::GetDisplayService(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_VI, "(STUBBED) called");
-
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IApplicationDisplayService>(nv_flinger);
-}
-
-VI_S::VI_S(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger)
- : ServiceFramework("vi:s"), nv_flinger(std::move(nv_flinger)) {
+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)) {
static const FunctionInfo functions[] = {
{1, &VI_S::GetDisplayService, "GetDisplayService"},
{3, nullptr, "GetDisplayServiceWithProxyNameExchange"},
@@ -27,5 +15,4 @@ VI_S::VI_S(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger)
RegisterHandlers(functions);
}
-} // namespace VI
-} // namespace Service
+} // namespace Service::VI
diff --git a/src/core/hle/service/vi/vi_s.h b/src/core/hle/service/vi/vi_s.h
index 6978fd700..8f16f804f 100644
--- a/src/core/hle/service/vi/vi_s.h
+++ b/src/core/hle/service/vi/vi_s.h
@@ -4,26 +4,13 @@
#pragma once
-#include <memory>
-#include "core/hle/service/service.h"
+#include "core/hle/service/vi/vi.h"
-namespace Service {
-namespace NVFlinger {
-class NVFlinger;
-}
+namespace Service::VI {
-namespace VI {
-
-class VI_S final : public ServiceFramework<VI_S> {
+class VI_S final : public Module::Interface {
public:
- VI_S(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger);
- ~VI_S() = default;
-
-private:
- void GetDisplayService(Kernel::HLERequestContext& ctx);
-
- std::shared_ptr<NVFlinger::NVFlinger> nv_flinger;
+ explicit VI_S(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger);
};
-} // namespace VI
-} // namespace Service
+} // namespace Service::VI
diff --git a/src/core/hle/service/vi/vi_u.cpp b/src/core/hle/service/vi/vi_u.cpp
index f5568383b..b84aed1d5 100644
--- a/src/core/hle/service/vi/vi_u.cpp
+++ b/src/core/hle/service/vi/vi_u.cpp
@@ -2,30 +2,16 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include "common/logging/log.h"
-#include "core/hle/ipc_helpers.h"
-#include "core/hle/service/vi/vi.h"
#include "core/hle/service/vi/vi_u.h"
-namespace Service {
-namespace VI {
+namespace Service::VI {
-void VI_U::GetDisplayService(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_VI, "(STUBBED) called");
-
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IApplicationDisplayService>(nv_flinger);
-}
-
-VI_U::VI_U(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger)
- : ServiceFramework("vi:u"), nv_flinger(std::move(nv_flinger)) {
+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)) {
static const FunctionInfo functions[] = {
{0, &VI_U::GetDisplayService, "GetDisplayService"},
- {3, nullptr, "GetDisplayServiceWithProxyNameExchange"},
};
RegisterHandlers(functions);
}
-} // namespace VI
-} // namespace Service
+} // namespace Service::VI
diff --git a/src/core/hle/service/vi/vi_u.h b/src/core/hle/service/vi/vi_u.h
index b3e9c094d..e9b4f76b2 100644
--- a/src/core/hle/service/vi/vi_u.h
+++ b/src/core/hle/service/vi/vi_u.h
@@ -4,26 +4,13 @@
#pragma once
-#include <memory>
-#include "core/hle/service/service.h"
+#include "core/hle/service/vi/vi.h"
-namespace Service {
-namespace NVFlinger {
-class NVFlinger;
-}
+namespace Service::VI {
-namespace VI {
-
-class VI_U final : public ServiceFramework<VI_U> {
+class VI_U final : public Module::Interface {
public:
- VI_U(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger);
- ~VI_U() = default;
-
-private:
- void GetDisplayService(Kernel::HLERequestContext& ctx);
-
- std::shared_ptr<NVFlinger::NVFlinger> nv_flinger;
+ explicit VI_U(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger);
};
-} // namespace VI
-} // namespace Service
+} // namespace Service::VI
diff --git a/src/core/hle/shared_page.cpp b/src/core/hle/shared_page.cpp
index bba4a0715..9ed8ab249 100644
--- a/src/core/hle/shared_page.cpp
+++ b/src/core/hle/shared_page.cpp
@@ -56,13 +56,14 @@ static void UpdateTimeCallback(u64 userdata, int cycles_late) {
date_time.date_time = GetSystemTime();
date_time.update_tick = CoreTiming::GetTicks();
- date_time.tick_to_second_coefficient = BASE_CLOCK_RATE;
+ date_time.tick_to_second_coefficient = CoreTiming::BASE_CLOCK_RATE;
date_time.tick_offset = 0;
++shared_page.date_time_counter;
// system time is updated hourly
- CoreTiming::ScheduleEvent(msToCycles(60 * 60 * 1000) - cycles_late, update_time_event);
+ CoreTiming::ScheduleEvent(CoreTiming::msToCycles(60 * 60 * 1000) - cycles_late,
+ update_time_event);
}
void Init() {
diff --git a/src/core/hw/hw.cpp b/src/core/hw/hw.cpp
index 0db604c76..8fc91dc9c 100644
--- a/src/core/hw/hw.cpp
+++ b/src/core/hw/hw.cpp
@@ -33,7 +33,8 @@ inline void Read(T& var, const u32 addr) {
LCD::Read(var, addr);
break;
default:
- LOG_ERROR(HW_Memory, "unknown Read%lu @ 0x%08X", sizeof(var) * 8, addr);
+ NGLOG_ERROR(HW_Memory, "Unknown Read{} @ 0x{:08X}", sizeof(var) * 8, addr);
+ break;
}
}
@@ -61,7 +62,8 @@ inline void Write(u32 addr, const T data) {
LCD::Write(addr, data);
break;
default:
- LOG_ERROR(HW_Memory, "unknown Write%lu 0x%08X @ 0x%08X", sizeof(data) * 8, (u32)data, addr);
+ NGLOG_ERROR(HW_Memory, "Unknown Write{} 0x{:08X} @ 0x{:08X}", sizeof(data) * 8, data, addr);
+ break;
}
}
@@ -83,12 +85,12 @@ void Update() {}
/// Initialize hardware
void Init() {
LCD::Init();
- LOG_DEBUG(HW, "initialized OK");
+ NGLOG_DEBUG(HW, "Initialized OK");
}
/// Shutdown hardware
void Shutdown() {
LCD::Shutdown();
- LOG_DEBUG(HW, "shutdown OK");
+ NGLOG_DEBUG(HW, "Shutdown OK");
}
} // namespace HW
diff --git a/src/core/hw/lcd.cpp b/src/core/hw/lcd.cpp
index 690079b65..e8525efde 100644
--- a/src/core/hw/lcd.cpp
+++ b/src/core/hw/lcd.cpp
@@ -20,7 +20,7 @@ inline void Read(T& var, const u32 raw_addr) {
// Reads other than u32 are untested, so I'd rather have them abort than silently fail
if (index >= 0x400 || !std::is_same<T, u32>::value) {
- LOG_ERROR(HW_LCD, "unknown Read%lu @ 0x%08X", sizeof(var) * 8, addr);
+ NGLOG_ERROR(HW_LCD, "Unknown Read{} @ 0x{:08X}", sizeof(var) * 8, addr);
return;
}
@@ -34,7 +34,7 @@ inline void Write(u32 addr, const T data) {
// Writes other than u32 are untested, so I'd rather have them abort than silently fail
if (index >= 0x400 || !std::is_same<T, u32>::value) {
- LOG_ERROR(HW_LCD, "unknown Write%lu 0x%08X @ 0x%08X", sizeof(data) * 8, (u32)data, addr);
+ NGLOG_ERROR(HW_LCD, "Unknown Write{} 0x{:08X} @ 0x{:08X}", sizeof(data) * 8, data, addr);
return;
}
@@ -56,12 +56,12 @@ template void Write<u8>(u32 addr, const u8 data);
/// Initialize hardware
void Init() {
memset(&g_regs, 0, sizeof(g_regs));
- LOG_DEBUG(HW_LCD, "initialized OK");
+ NGLOG_DEBUG(HW_LCD, "Initialized OK");
}
/// Shutdown hardware
void Shutdown() {
- LOG_DEBUG(HW_LCD, "shutdown OK");
+ NGLOG_DEBUG(HW_LCD, "Shutdown OK");
}
} // namespace LCD
diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp
index 864cf25cd..b01b2caf6 100644
--- a/src/core/loader/deconstructed_rom_directory.cpp
+++ b/src/core/loader/deconstructed_rom_directory.cpp
@@ -76,7 +76,7 @@ FileType AppLoader_DeconstructedRomDirectory::IdentifyType(FileUtil::IOFile& fil
} else if (Common::ToLower(virtual_name) == "sdk") {
is_sdk_found = true;
} else {
- // Contrinue searching
+ // Continue searching
return true;
}
@@ -110,8 +110,6 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load(
return ResultStatus::Error;
}
- process = Kernel::Process::Create("main");
-
const std::string directory = filepath.substr(0, filepath.find_last_of("/\\")) + DIR_SEP;
const std::string npdm_path = directory + DIR_SEP + "main.npdm";
@@ -121,20 +119,26 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load(
}
metadata.Print();
+ const FileSys::ProgramAddressSpaceType arch_bits{metadata.GetAddressSpaceType()};
+ if (arch_bits == FileSys::ProgramAddressSpaceType::Is32Bit) {
+ return ResultStatus::ErrorUnsupportedArch;
+ }
+
// Load NSO modules
VAddr next_load_addr{Memory::PROCESS_IMAGE_VADDR};
for (const auto& module : {"rtld", "main", "subsdk0", "subsdk1", "subsdk2", "subsdk3",
"subsdk4", "subsdk5", "subsdk6", "subsdk7", "sdk"}) {
const std::string path = directory + DIR_SEP + module;
const VAddr load_addr = next_load_addr;
- next_load_addr = AppLoader_NSO::LoadModule(path, load_addr, metadata.GetTitleID());
+ next_load_addr = AppLoader_NSO::LoadModule(path, load_addr);
if (next_load_addr) {
- LOG_DEBUG(Loader, "loaded module %s @ 0x%" PRIx64, module, load_addr);
+ NGLOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", module, load_addr);
} else {
next_load_addr = load_addr;
}
}
+ process->program_id = metadata.GetTitleID();
process->svc_access_mask.set();
process->address_mappings = default_address_mappings;
process->resource_limit =
@@ -159,7 +163,7 @@ ResultStatus AppLoader_DeconstructedRomDirectory::ReadRomFS(
std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset, u64& size) {
if (filepath_romfs.empty()) {
- LOG_DEBUG(Loader, "No RomFS available");
+ NGLOG_DEBUG(Loader, "No RomFS available");
return ResultStatus::ErrorNotUsed;
}
@@ -172,8 +176,8 @@ ResultStatus AppLoader_DeconstructedRomDirectory::ReadRomFS(
offset = 0;
size = romfs_file->GetSize();
- LOG_DEBUG(Loader, "RomFS offset: 0x%016" PRIX64, offset);
- LOG_DEBUG(Loader, "RomFS size: 0x%016" PRIX64, size);
+ NGLOG_DEBUG(Loader, "RomFS offset: 0x{:016X}", offset);
+ NGLOG_DEBUG(Loader, "RomFS size: 0x{:016X}", size);
// Reset read pointer
file.Seek(0, SEEK_SET);
diff --git a/src/core/loader/elf.cpp b/src/core/loader/elf.cpp
index b87320656..e42d3a870 100644
--- a/src/core/loader/elf.cpp
+++ b/src/core/loader/elf.cpp
@@ -273,18 +273,18 @@ const char* ElfReader::GetSectionName(int section) const {
}
SharedPtr<CodeSet> ElfReader::LoadInto(u32 vaddr) {
- LOG_DEBUG(Loader, "String section: %i", header->e_shstrndx);
+ NGLOG_DEBUG(Loader, "String section: {}", header->e_shstrndx);
// Should we relocate?
relocate = (header->e_type != ET_EXEC);
if (relocate) {
- LOG_DEBUG(Loader, "Relocatable module");
+ NGLOG_DEBUG(Loader, "Relocatable module");
entryPoint += vaddr;
} else {
- LOG_DEBUG(Loader, "Prerelocated executable");
+ NGLOG_DEBUG(Loader, "Prerelocated executable");
}
- LOG_DEBUG(Loader, "%i segments:", header->e_phnum);
+ NGLOG_DEBUG(Loader, "{} segments:", header->e_phnum);
// First pass : Get the bits into RAM
u32 base_addr = relocate ? vaddr : 0;
@@ -300,12 +300,12 @@ SharedPtr<CodeSet> ElfReader::LoadInto(u32 vaddr) {
std::vector<u8> program_image(total_image_size);
size_t current_image_position = 0;
- SharedPtr<CodeSet> codeset = CodeSet::Create("", 0);
+ SharedPtr<CodeSet> codeset = CodeSet::Create("");
for (unsigned int i = 0; i < header->e_phnum; ++i) {
Elf32_Phdr* p = &segments[i];
- LOG_DEBUG(Loader, "Type: %i Vaddr: %08X Filesz: %8X Memsz: %8X ", p->p_type, p->p_vaddr,
- p->p_filesz, p->p_memsz);
+ NGLOG_DEBUG(Loader, "Type: {} Vaddr: {:08X} Filesz: {:08X} Memsz: {:08X} ", p->p_type,
+ p->p_vaddr, p->p_filesz, p->p_memsz);
if (p->p_type == PT_LOAD) {
CodeSet::Segment* codeset_segment;
@@ -317,16 +317,16 @@ SharedPtr<CodeSet> ElfReader::LoadInto(u32 vaddr) {
} else if (permission_flags == (PF_R | PF_W)) {
codeset_segment = &codeset->data;
} else {
- LOG_ERROR(Loader, "Unexpected ELF PT_LOAD segment id %u with flags %X", i,
- p->p_flags);
+ NGLOG_ERROR(Loader, "Unexpected ELF PT_LOAD segment id {} with flags {:X}", i,
+ p->p_flags);
continue;
}
if (codeset_segment->size != 0) {
- LOG_ERROR(Loader,
- "ELF has more than one segment of the same type. Skipping extra "
- "segment (id %i)",
- i);
+ NGLOG_ERROR(Loader,
+ "ELF has more than one segment of the same type. Skipping extra "
+ "segment (id {})",
+ i);
continue;
}
@@ -345,7 +345,7 @@ SharedPtr<CodeSet> ElfReader::LoadInto(u32 vaddr) {
codeset->entrypoint = base_addr + header->e_entry;
codeset->memory = std::make_shared<std::vector<u8>>(std::move(program_image));
- LOG_DEBUG(Loader, "Done loading.");
+ NGLOG_DEBUG(Loader, "Done loading.");
return codeset;
}
@@ -406,7 +406,6 @@ ResultStatus AppLoader_ELF::Load(Kernel::SharedPtr<Kernel::Process>& process) {
SharedPtr<CodeSet> codeset = elf_reader.LoadInto(Memory::PROCESS_IMAGE_VADDR);
codeset->name = filename;
- process = Kernel::Process::Create("main");
process->LoadModule(codeset, codeset->entrypoint);
process->svc_access_mask.set();
process->address_mappings = default_address_mappings;
@@ -415,7 +414,7 @@ ResultStatus AppLoader_ELF::Load(Kernel::SharedPtr<Kernel::Process>& process) {
process->resource_limit =
Kernel::ResourceLimit::GetForCategory(Kernel::ResourceLimitCategory::APPLICATION);
- process->Run(codeset->entrypoint, 48, Kernel::DEFAULT_STACK_SIZE);
+ process->Run(codeset->entrypoint, 48, Memory::DEFAULT_STACK_SIZE);
is_loaded = true;
return ResultStatus::Success;
diff --git a/src/core/loader/linker.cpp b/src/core/loader/linker.cpp
index 87cc65e91..c7be5f265 100644
--- a/src/core/loader/linker.cpp
+++ b/src/core/loader/linker.cpp
@@ -84,7 +84,7 @@ void Linker::WriteRelocations(std::vector<u8>& program_image, const std::vector<
}
break;
default:
- LOG_CRITICAL(Loader, "Unknown relocation type: %d", rela.type);
+ NGLOG_CRITICAL(Loader, "Unknown relocation type: {}", static_cast<int>(rela.type));
break;
}
}
@@ -141,7 +141,7 @@ void Linker::ResolveImports() {
if (search != exports.end()) {
Memory::Write64(import.second.ea, search->second + import.second.addend);
} else {
- LOG_ERROR(Loader, "Unresolved import: %s", import.first.c_str());
+ NGLOG_ERROR(Loader, "Unresolved import: {}", import.first);
}
}
}
diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp
index 2ec08506d..6a4fd38cb 100644
--- a/src/core/loader/loader.cpp
+++ b/src/core/loader/loader.cpp
@@ -41,7 +41,7 @@ FileType IdentifyFile(FileUtil::IOFile& file, const std::string& filepath) {
FileType IdentifyFile(const std::string& file_name) {
FileUtil::IOFile file(file_name, "rb");
if (!file.IsOpen()) {
- LOG_ERROR(Loader, "Failed to load file %s", file_name.c_str());
+ NGLOG_ERROR(Loader, "Failed to load file {}", file_name);
return FileType::Unknown;
}
@@ -116,7 +116,7 @@ static std::unique_ptr<AppLoader> GetFileLoader(FileUtil::IOFile&& file, FileTyp
std::unique_ptr<AppLoader> GetLoader(const std::string& filename) {
FileUtil::IOFile file(filename, "rb");
if (!file.IsOpen()) {
- LOG_ERROR(Loader, "Failed to load file %s", filename.c_str());
+ NGLOG_ERROR(Loader, "Failed to load file {}", filename);
return nullptr;
}
@@ -127,12 +127,12 @@ std::unique_ptr<AppLoader> GetLoader(const std::string& filename) {
FileType filename_type = GuessFromExtension(filename_extension);
if (type != filename_type) {
- LOG_WARNING(Loader, "File %s has a different type than its extension.", filename.c_str());
+ NGLOG_WARNING(Loader, "File {} has a different type than its extension.", filename);
if (FileType::Unknown == type)
type = filename_type;
}
- LOG_DEBUG(Loader, "Loading file %s as %s...", filename.c_str(), GetFileTypeString(type));
+ NGLOG_DEBUG(Loader, "Loading file {} as {}...", filename, GetFileTypeString(type));
return GetFileLoader(std::move(file), type, filename_filename, filename);
}
diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h
index dd44ee9a6..b1aabb1cb 100644
--- a/src/core/loader/loader.h
+++ b/src/core/loader/loader.h
@@ -72,6 +72,7 @@ enum class ResultStatus {
ErrorAlreadyLoaded,
ErrorMemoryAllocationFailed,
ErrorEncrypted,
+ ErrorUnsupportedArch,
};
/// Interface for loading an application
diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp
index 6f8a2f21e..3853cfa1a 100644
--- a/src/core/loader/nro.cpp
+++ b/src/core/loader/nro.cpp
@@ -8,6 +8,7 @@
#include "common/file_util.h"
#include "common/logging/log.h"
#include "common/swap.h"
+#include "core/core.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/resource_limit.h"
#include "core/loader/nro.h"
@@ -83,7 +84,7 @@ bool AppLoader_NRO::LoadNro(const std::string& path, VAddr load_base) {
}
// Build program image
- Kernel::SharedPtr<Kernel::CodeSet> codeset = Kernel::CodeSet::Create("", 0);
+ Kernel::SharedPtr<Kernel::CodeSet> codeset = Kernel::CodeSet::Create("");
std::vector<u8> program_image;
program_image.resize(PageAlignSize(nro_header.file_size));
file.Seek(0, SEEK_SET);
@@ -112,7 +113,7 @@ bool AppLoader_NRO::LoadNro(const std::string& path, VAddr load_base) {
// Load codeset for current process
codeset->name = path;
codeset->memory = std::make_shared<std::vector<u8>>(std::move(program_image));
- Kernel::g_current_process->LoadModule(codeset, load_base);
+ Core::CurrentProcess()->LoadModule(codeset, load_base);
return true;
}
@@ -125,8 +126,6 @@ ResultStatus AppLoader_NRO::Load(Kernel::SharedPtr<Kernel::Process>& process) {
return ResultStatus::Error;
}
- process = Kernel::Process::Create("main");
-
// Load NRO
static constexpr VAddr base_addr{Memory::PROCESS_IMAGE_VADDR};
@@ -138,7 +137,7 @@ ResultStatus AppLoader_NRO::Load(Kernel::SharedPtr<Kernel::Process>& process) {
process->address_mappings = default_address_mappings;
process->resource_limit =
Kernel::ResourceLimit::GetForCategory(Kernel::ResourceLimitCategory::APPLICATION);
- process->Run(base_addr, 48, Kernel::DEFAULT_STACK_SIZE);
+ process->Run(base_addr, THREADPRIO_DEFAULT, Memory::DEFAULT_STACK_SIZE);
is_loaded = true;
return ResultStatus::Success;
diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp
index 7f8d24dd6..01be9e217 100644
--- a/src/core/loader/nso.cpp
+++ b/src/core/loader/nso.cpp
@@ -9,6 +9,7 @@
#include "common/file_util.h"
#include "common/logging/log.h"
#include "common/swap.h"
+#include "core/core.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/resource_limit.h"
#include "core/loader/nso.h"
@@ -72,7 +73,7 @@ static std::vector<u8> ReadSegment(FileUtil::IOFile& file, const NsoSegmentHeade
file.Seek(header.offset, SEEK_SET);
if (compressed_size != file.ReadBytes(compressed_data.data(), compressed_size)) {
- LOG_CRITICAL(Loader, "Failed to read %d NSO LZ4 compressed bytes", compressed_size);
+ NGLOG_CRITICAL(Loader, "Failed to read {} NSO LZ4 compressed bytes", compressed_size);
return {};
}
@@ -83,7 +84,7 @@ static std::vector<u8> ReadSegment(FileUtil::IOFile& file, const NsoSegmentHeade
reinterpret_cast<char*>(uncompressed_data.data()), compressed_size, header.size);
ASSERT_MSG(bytes_uncompressed == header.size && bytes_uncompressed == uncompressed_data.size(),
- "%d != %u != %zu", bytes_uncompressed, header.size, uncompressed_data.size());
+ "{} != {} != {}", bytes_uncompressed, header.size, uncompressed_data.size());
return uncompressed_data;
}
@@ -92,7 +93,7 @@ static constexpr u32 PageAlignSize(u32 size) {
return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK;
}
-VAddr AppLoader_NSO::LoadModule(const std::string& path, VAddr load_base, u64 tid) {
+VAddr AppLoader_NSO::LoadModule(const std::string& path, VAddr load_base) {
FileUtil::IOFile file(path, "rb");
if (!file.IsOpen()) {
return {};
@@ -109,7 +110,7 @@ VAddr AppLoader_NSO::LoadModule(const std::string& path, VAddr load_base, u64 ti
}
// Build program image
- Kernel::SharedPtr<Kernel::CodeSet> codeset = Kernel::CodeSet::Create("", tid);
+ Kernel::SharedPtr<Kernel::CodeSet> codeset = Kernel::CodeSet::Create("");
std::vector<u8> program_image;
for (int i = 0; i < nso_header.segments.size(); ++i) {
std::vector<u8> data =
@@ -142,7 +143,7 @@ VAddr AppLoader_NSO::LoadModule(const std::string& path, VAddr load_base, u64 ti
// Load codeset for current process
codeset->name = path;
codeset->memory = std::make_shared<std::vector<u8>>(std::move(program_image));
- Kernel::g_current_process->LoadModule(codeset, load_base);
+ Core::CurrentProcess()->LoadModule(codeset, load_base);
return load_base + image_size;
}
@@ -155,18 +156,15 @@ ResultStatus AppLoader_NSO::Load(Kernel::SharedPtr<Kernel::Process>& process) {
return ResultStatus::Error;
}
- process = Kernel::Process::Create("main");
-
// Load module
- LoadModule(filepath, Memory::PROCESS_IMAGE_VADDR, 0);
- LOG_DEBUG(Loader, "loaded module %s @ 0x%" PRIx64, filepath.c_str(),
- Memory::PROCESS_IMAGE_VADDR);
+ LoadModule(filepath, Memory::PROCESS_IMAGE_VADDR);
+ NGLOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", filepath, Memory::PROCESS_IMAGE_VADDR);
process->svc_access_mask.set();
process->address_mappings = default_address_mappings;
process->resource_limit =
Kernel::ResourceLimit::GetForCategory(Kernel::ResourceLimitCategory::APPLICATION);
- process->Run(Memory::PROCESS_IMAGE_VADDR, 48, Kernel::DEFAULT_STACK_SIZE);
+ process->Run(Memory::PROCESS_IMAGE_VADDR, THREADPRIO_DEFAULT, Memory::DEFAULT_STACK_SIZE);
is_loaded = true;
return ResultStatus::Success;
diff --git a/src/core/loader/nso.h b/src/core/loader/nso.h
index 14eb1d87e..1ae30a824 100644
--- a/src/core/loader/nso.h
+++ b/src/core/loader/nso.h
@@ -29,7 +29,7 @@ public:
return IdentifyType(file, filepath);
}
- static VAddr LoadModule(const std::string& path, VAddr load_base, u64 tid);
+ static VAddr LoadModule(const std::string& path, VAddr load_base);
ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override;
diff --git a/src/core/memory.cpp b/src/core/memory.cpp
index ce62666d7..3b81acd63 100644
--- a/src/core/memory.cpp
+++ b/src/core/memory.cpp
@@ -4,7 +4,6 @@
#include <algorithm>
#include <array>
-#include <cinttypes>
#include <cstring>
#include <boost/optional.hpp>
#include "common/assert.h"
@@ -15,6 +14,7 @@
#include "core/core.h"
#include "core/hle/kernel/memory.h"
#include "core/hle/kernel/process.h"
+#include "core/hle/lock.h"
#include "core/memory.h"
#include "core/memory_setup.h"
#include "video_core/renderer_base.h"
@@ -23,14 +23,18 @@
namespace Memory {
static std::array<u8, Memory::VRAM_SIZE> vram;
-static std::array<u8, Memory::N3DS_EXTRA_RAM_SIZE> n3ds_extra_ram;
static PageTable* current_page_table = nullptr;
void SetCurrentPageTable(PageTable* page_table) {
current_page_table = page_table;
- if (Core::System::GetInstance().IsPoweredOn()) {
- Core::CPU().PageTableChanged();
+
+ auto& system = Core::System::GetInstance();
+ if (system.IsPoweredOn()) {
+ system.ArmInterface(0).PageTableChanged();
+ system.ArmInterface(1).PageTableChanged();
+ system.ArmInterface(2).PageTableChanged();
+ system.ArmInterface(3).PageTableChanged();
}
}
@@ -39,12 +43,15 @@ PageTable* GetCurrentPageTable() {
}
static void MapPages(PageTable& page_table, VAddr base, u64 size, u8* memory, PageType type) {
- LOG_DEBUG(HW_Memory, "Mapping %p onto %016" PRIX64 "-%016" PRIX64, memory, base * PAGE_SIZE,
- (base + size) * PAGE_SIZE);
+ NGLOG_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);
VAddr end = base + size;
while (base != end) {
- ASSERT_MSG(base < PAGE_TABLE_NUM_ENTRIES, "out of range mapping at %016" PRIX64, base);
+ ASSERT_MSG(base < PAGE_TABLE_NUM_ENTRIES, "out of range mapping at {:016X}", base);
page_table.attributes[base] = type;
page_table.pointers[base] = memory;
@@ -56,14 +63,14 @@ static void MapPages(PageTable& page_table, VAddr base, u64 size, u8* memory, Pa
}
void MapMemoryRegion(PageTable& page_table, VAddr base, u64 size, u8* target) {
- ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: %016" PRIX64, size);
- ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: %016" PRIX64, base);
+ 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);
}
void MapIoRegion(PageTable& page_table, VAddr base, u64 size, MemoryHookPointer mmio_handler) {
- ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: %016" PRIX64, size);
- ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: %016" PRIX64, base);
+ 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);
auto interval = boost::icl::discrete_interval<VAddr>::closed(base, base + size - 1);
@@ -72,8 +79,8 @@ void MapIoRegion(PageTable& page_table, VAddr base, u64 size, MemoryHookPointer
}
void UnmapRegion(PageTable& page_table, VAddr base, u64 size) {
- ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: %016" PRIX64, size);
- ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: %016" PRIX64, base);
+ 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);
auto interval = boost::icl::discrete_interval<VAddr>::closed(base, base + size - 1);
@@ -109,100 +116,129 @@ static std::set<MemoryHookPointer> GetSpecialHandlers(const PageTable& page_tabl
}
static std::set<MemoryHookPointer> GetSpecialHandlers(VAddr vaddr, u64 size) {
- const PageTable& page_table = Kernel::g_current_process->vm_manager.page_table;
+ const PageTable& page_table = Core::CurrentProcess()->vm_manager.page_table;
return GetSpecialHandlers(page_table, vaddr, size);
}
-template <typename T>
-boost::optional<T> ReadSpecial(VAddr addr);
+/**
+ * Gets a pointer to the exact memory at the virtual address (i.e. not page aligned)
+ * using a VMA from the current process
+ */
+static u8* GetPointerFromVMA(const Kernel::Process& process, VAddr vaddr) {
+ u8* direct_pointer = nullptr;
+
+ auto& vm_manager = process.vm_manager;
+
+ auto it = vm_manager.FindVMA(vaddr);
+ ASSERT(it != vm_manager.vma_map.end());
+
+ auto& vma = it->second;
+ switch (vma.type) {
+ case Kernel::VMAType::AllocatedMemoryBlock:
+ direct_pointer = vma.backing_block->data() + vma.offset;
+ break;
+ case Kernel::VMAType::BackingMemory:
+ direct_pointer = vma.backing_memory;
+ break;
+ case Kernel::VMAType::Free:
+ return nullptr;
+ default:
+ UNREACHABLE();
+ }
+
+ return direct_pointer + (vaddr - vma.base);
+}
+
+/**
+ * Gets a pointer to the exact memory at the virtual address (i.e. not page aligned)
+ * using a VMA from the current process.
+ */
+static u8* GetPointerFromVMA(VAddr vaddr) {
+ return GetPointerFromVMA(*Core::CurrentProcess(), vaddr);
+}
template <typename T>
T Read(const VAddr vaddr) {
- if ((vaddr >> PAGE_BITS) >= PAGE_TABLE_NUM_ENTRIES) {
- LOG_ERROR(HW_Memory, "Read%lu after page table @ 0x%016" PRIX64, sizeof(T) * 8, vaddr);
- return 0;
+ const u8* page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS];
+ if (page_pointer) {
+ // NOTE: Avoid adding any extra logic to this fast-path block
+ T value;
+ std::memcpy(&value, &page_pointer[vaddr & PAGE_MASK], sizeof(T));
+ return value;
}
- const PageType type = current_page_table->attributes[vaddr >> PAGE_BITS];
+ // The memory access might do an MMIO or cached access, so we have to lock the HLE kernel state
+ std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
+
+ PageType type = current_page_table->attributes[vaddr >> PAGE_BITS];
switch (type) {
case PageType::Unmapped:
- LOG_ERROR(HW_Memory, "unmapped Read%zu @ 0x%016" PRIX64, sizeof(T) * 8, vaddr);
+ NGLOG_ERROR(HW_Memory, "Unmapped Read{} @ 0x{:08X}", sizeof(T) * 8, vaddr);
return 0;
- case PageType::Special: {
- if (auto result = ReadSpecial<T>(vaddr))
- return *result;
- [[fallthrough]];
- }
- case PageType::Memory: {
- const u8* page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS];
- ASSERT_MSG(page_pointer, "Mapped memory page without a pointer @ %016" PRIX64, vaddr);
+ case PageType::Memory:
+ ASSERT_MSG(false, "Mapped memory page without a pointer @ {:016X}", vaddr);
+ break;
+ case PageType::RasterizerCachedMemory: {
+ RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::Flush);
T value;
- std::memcpy(&value, &page_pointer[vaddr & PAGE_MASK], sizeof(T));
+ std::memcpy(&value, GetPointerFromVMA(vaddr), sizeof(T));
return value;
}
+ default:
+ UNREACHABLE();
}
- UNREACHABLE();
- return 0;
}
template <typename T>
-bool WriteSpecial(VAddr addr, const T data);
-
-template <typename T>
void Write(const VAddr vaddr, const T data) {
- if ((vaddr >> PAGE_BITS) >= PAGE_TABLE_NUM_ENTRIES) {
- LOG_ERROR(HW_Memory, "Write%lu after page table 0x%08X @ 0x%016" PRIX64, sizeof(data) * 8,
- (u32)data, vaddr);
+ u8* page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS];
+ if (page_pointer) {
+ // NOTE: Avoid adding any extra logic to this fast-path block
+ std::memcpy(&page_pointer[vaddr & PAGE_MASK], &data, sizeof(T));
return;
}
- const PageType type = current_page_table->attributes[vaddr >> PAGE_BITS];
+ // The memory access might do an MMIO or cached access, so we have to lock the HLE kernel state
+ std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
+
+ PageType type = current_page_table->attributes[vaddr >> PAGE_BITS];
switch (type) {
case PageType::Unmapped:
- LOG_ERROR(HW_Memory, "unmapped Write%zu 0x%08X @ 0x%016" PRIX64, sizeof(data) * 8,
- static_cast<u32>(data), vaddr);
- return;
- case PageType::Special: {
- if (WriteSpecial<T>(vaddr, data))
- return;
- [[fallthrough]];
- }
- case PageType::Memory: {
- u8* page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS];
- ASSERT_MSG(page_pointer, "Mapped memory page without a pointer @ %016" PRIX64, vaddr);
- std::memcpy(&page_pointer[vaddr & PAGE_MASK], &data, sizeof(T));
+ NGLOG_ERROR(HW_Memory, "Unmapped Write{} 0x{:08X} @ 0x{:016X}", sizeof(data) * 8,
+ static_cast<u32>(data), vaddr);
return;
+ case 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));
+ break;
}
+ default:
+ UNREACHABLE();
}
- UNREACHABLE();
}
bool IsValidVirtualAddress(const Kernel::Process& process, const VAddr vaddr) {
auto& page_table = process.vm_manager.page_table;
- if ((vaddr >> PAGE_BITS) >= PAGE_TABLE_NUM_ENTRIES)
- return false;
+ const u8* page_pointer = page_table.pointers[vaddr >> PAGE_BITS];
+ if (page_pointer)
+ return true;
- const PageType type = current_page_table->attributes[vaddr >> PAGE_BITS];
- switch (type) {
- case PageType::Unmapped:
- return false;
- case PageType::Memory:
+ if (page_table.attributes[vaddr >> PAGE_BITS] == PageType::RasterizerCachedMemory)
return true;
- case PageType::Special: {
- for (auto handler : GetSpecialHandlers(page_table, vaddr, 1))
- if (auto result = handler->IsValidAddress(vaddr))
- return *result;
- return current_page_table->pointers[vaddr >> PAGE_BITS] != nullptr;
- }
- }
- UNREACHABLE();
+
+ if (page_table.attributes[vaddr >> PAGE_BITS] != PageType::Special)
+ return false;
+
return false;
}
bool IsValidVirtualAddress(const VAddr vaddr) {
- return IsValidVirtualAddress(*Kernel::g_current_process, vaddr);
+ return IsValidVirtualAddress(*Core::CurrentProcess(), vaddr);
}
bool IsValidPhysicalAddress(const PAddr paddr) {
@@ -215,7 +251,11 @@ u8* GetPointer(const VAddr vaddr) {
return page_pointer + (vaddr & PAGE_MASK);
}
- LOG_ERROR(HW_Memory, "unknown GetPointer @ 0x%016" PRIx64, vaddr);
+ if (current_page_table->attributes[vaddr >> PAGE_BITS] == PageType::RasterizerCachedMemory) {
+ return GetPointerFromVMA(vaddr);
+ }
+
+ NGLOG_ERROR(HW_Memory, "Unknown GetPointer @ 0x{:016X}", vaddr);
return nullptr;
}
@@ -244,7 +284,6 @@ u8* GetPhysicalPointer(PAddr address) {
{IO_AREA_PADDR, IO_AREA_SIZE},
{DSP_RAM_PADDR, DSP_RAM_SIZE},
{FCRAM_PADDR, FCRAM_N3DS_SIZE},
- {N3DS_EXTRA_RAM_PADDR, N3DS_EXTRA_RAM_SIZE},
};
const auto area =
@@ -253,13 +292,12 @@ u8* GetPhysicalPointer(PAddr address) {
});
if (area == std::end(memory_areas)) {
- LOG_ERROR(HW_Memory, "unknown GetPhysicalPointer @ 0x%016" PRIX64, address);
+ NGLOG_ERROR(HW_Memory, "Unknown GetPhysicalPointer @ 0x{:016X}", address);
return nullptr;
}
if (area->paddr_base == IO_AREA_PADDR) {
- LOG_ERROR(HW_Memory, "MMIO mappings are not supported yet. phys_addr=0x%016" PRIX64,
- address);
+ NGLOG_ERROR(HW_Memory, "MMIO mappings are not supported yet. phys_addr={:016X}", address);
return nullptr;
}
@@ -283,9 +321,6 @@ u8* GetPhysicalPointer(PAddr address) {
}
ASSERT_MSG(target_pointer != nullptr, "Invalid FCRAM address");
break;
- case N3DS_EXTRA_RAM_PADDR:
- target_pointer = n3ds_extra_ram.data() + offset_into_region;
- break;
default:
UNREACHABLE();
}
@@ -293,6 +328,127 @@ u8* GetPhysicalPointer(PAddr address) {
return target_pointer;
}
+void RasterizerMarkRegionCached(Tegra::GPUVAddr gpu_addr, u64 size, bool cached) {
+ if (gpu_addr == 0) {
+ return;
+ }
+
+ // Iterate over a contiguous CPU address space, which corresponds to the specified GPU address
+ // space, marking the region as un/cached. The region is marked un/cached at a granularity of
+ // CPU pages, hence why we iterate on a CPU page basis (note: GPU page size is different). This
+ // assumes the specified GPU address region is contiguous as well.
+
+ u64 num_pages = ((gpu_addr + size - 1) >> PAGE_BITS) - (gpu_addr >> PAGE_BITS) + 1;
+ for (unsigned i = 0; i < num_pages; ++i, gpu_addr += PAGE_SIZE) {
+ boost::optional<VAddr> maybe_vaddr =
+ Core::System::GetInstance().GPU().memory_manager->GpuToCpuAddress(gpu_addr);
+ // The GPU <-> CPU virtual memory mapping is not 1:1
+ if (!maybe_vaddr) {
+ NGLOG_ERROR(HW_Memory,
+ "Trying to flush a cached region to an invalid physical address {:016X}",
+ gpu_addr);
+ continue;
+ }
+ VAddr vaddr = *maybe_vaddr;
+
+ 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:
+ // 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;
+ current_page_table->pointers[vaddr >> PAGE_BITS] = nullptr;
+ break;
+ case 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;
+ default:
+ UNREACHABLE();
+ }
+ } else {
+ // Switch page type to uncached if now uncached
+ switch (page_type) {
+ case 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:
+ // 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: {
+ 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;
+ } else {
+ page_type = PageType::Memory;
+ current_page_table->pointers[vaddr >> PAGE_BITS] = pointer;
+ }
+ break;
+ }
+ default:
+ UNREACHABLE();
+ }
+ }
+ }
+}
+
+void RasterizerFlushVirtualRegion(VAddr start, u64 size, FlushMode mode) {
+ // Since pages are unmapped on shutdown after video core is shutdown, the renderer may be
+ // null here
+ if (VideoCore::g_renderer == nullptr) {
+ return;
+ }
+
+ VAddr end = start + size;
+
+ auto CheckRegion = [&](VAddr region_start, VAddr region_end) {
+ if (start >= region_end || end <= region_start) {
+ // No overlap with region
+ return;
+ }
+
+ VAddr overlap_start = std::max(start, region_start);
+ VAddr overlap_end = std::min(end, region_end);
+
+ std::vector<Tegra::GPUVAddr> gpu_addresses =
+ Core::System::GetInstance().GPU().memory_manager->CpuToGpuAddress(overlap_start);
+
+ if (gpu_addresses.empty()) {
+ return;
+ }
+
+ u64 overlap_size = overlap_end - overlap_start;
+
+ for (const auto& gpu_address : gpu_addresses) {
+ auto* rasterizer = VideoCore::g_renderer->Rasterizer();
+ switch (mode) {
+ case FlushMode::Flush:
+ rasterizer->FlushRegion(gpu_address, overlap_size);
+ break;
+ case FlushMode::Invalidate:
+ rasterizer->InvalidateRegion(gpu_address, overlap_size);
+ break;
+ case FlushMode::FlushAndInvalidate:
+ rasterizer->FlushAndInvalidateRegion(gpu_address, overlap_size);
+ break;
+ }
+ }
+ };
+
+ CheckRegion(PROCESS_IMAGE_VADDR, PROCESS_IMAGE_VADDR_END);
+ CheckRegion(HEAP_VADDR, HEAP_VADDR_END);
+}
+
u8 Read8(const VAddr addr) {
return Read<u8>(addr);
}
@@ -309,17 +465,6 @@ u64 Read64(const VAddr addr) {
return Read<u64_le>(addr);
}
-static bool ReadSpecialBlock(const Kernel::Process& process, const VAddr src_addr,
- void* dest_buffer, const size_t size) {
- auto& page_table = process.vm_manager.page_table;
- for (const auto& handler : GetSpecialHandlers(page_table, src_addr, size)) {
- if (handler->ReadBlock(src_addr, dest_buffer, size)) {
- return true;
- }
- }
- return false;
-}
-
void ReadBlock(const Kernel::Process& process, const VAddr src_addr, void* dest_buffer,
const size_t size) {
auto& page_table = process.vm_manager.page_table;
@@ -329,21 +474,17 @@ void ReadBlock(const Kernel::Process& process, const VAddr src_addr, void* dest_
size_t page_offset = src_addr & PAGE_MASK;
while (remaining_size > 0) {
- const size_t copy_amount = std::min<size_t>(PAGE_SIZE - page_offset, remaining_size);
+ const size_t copy_amount =
+ std::min(static_cast<size_t>(PAGE_SIZE) - page_offset, remaining_size);
const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset);
switch (page_table.attributes[page_index]) {
- case PageType::Unmapped:
- LOG_ERROR(HW_Memory,
- "unmapped ReadBlock @ 0x%016" PRIX64 " (start address = 0x%" PRIx64
- ", size = %zu)",
- current_vaddr, src_addr, size);
+ case PageType::Unmapped: {
+ NGLOG_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::Special: {
- if (ReadSpecialBlock(process, current_vaddr, dest_buffer, copy_amount))
- break;
- [[fallthrough]];
}
case PageType::Memory: {
DEBUG_ASSERT(page_table.pointers[page_index]);
@@ -352,6 +493,12 @@ void ReadBlock(const Kernel::Process& process, const VAddr src_addr, void* dest_
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);
+ break;
+ }
default:
UNREACHABLE();
}
@@ -364,7 +511,7 @@ void ReadBlock(const Kernel::Process& process, const VAddr src_addr, void* dest_
}
void ReadBlock(const VAddr src_addr, void* dest_buffer, const size_t size) {
- ReadBlock(*Kernel::g_current_process, src_addr, dest_buffer, size);
+ ReadBlock(*Core::CurrentProcess(), src_addr, dest_buffer, size);
}
void Write8(const VAddr addr, const u8 data) {
@@ -383,17 +530,6 @@ void Write64(const VAddr addr, const u64 data) {
Write<u64_le>(addr, data);
}
-static bool WriteSpecialBlock(const Kernel::Process& process, const VAddr dest_addr,
- const void* src_buffer, const size_t size) {
- auto& page_table = process.vm_manager.page_table;
- for (const auto& handler : GetSpecialHandlers(page_table, dest_addr, size)) {
- if (handler->WriteBlock(dest_addr, src_buffer, size)) {
- return true;
- }
- }
- return false;
-}
-
void WriteBlock(const Kernel::Process& process, const VAddr dest_addr, const void* src_buffer,
const size_t size) {
auto& page_table = process.vm_manager.page_table;
@@ -402,20 +538,17 @@ void WriteBlock(const Kernel::Process& process, const VAddr dest_addr, const voi
size_t page_offset = dest_addr & PAGE_MASK;
while (remaining_size > 0) {
- const size_t copy_amount = std::min<size_t>(PAGE_SIZE - page_offset, remaining_size);
+ const size_t copy_amount =
+ std::min(static_cast<size_t>(PAGE_SIZE) - page_offset, remaining_size);
const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset);
switch (page_table.attributes[page_index]) {
- case PageType::Unmapped:
- LOG_ERROR(HW_Memory,
- "unmapped WriteBlock @ 0x%016" PRIX64 " (start address = 0x%016" PRIX64
- ", size = %zu)",
- current_vaddr, dest_addr, size);
+ case PageType::Unmapped: {
+ NGLOG_ERROR(HW_Memory,
+ "Unmapped WriteBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})",
+ current_vaddr, dest_addr, size);
break;
- case PageType::Special:
- if (WriteSpecialBlock(process, current_vaddr, src_buffer, copy_amount))
- break;
- [[fallthrough]];
+ }
case PageType::Memory: {
DEBUG_ASSERT(page_table.pointers[page_index]);
@@ -423,6 +556,12 @@ void WriteBlock(const Kernel::Process& process, const VAddr dest_addr, const voi
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);
+ break;
+ }
default:
UNREACHABLE();
}
@@ -435,12 +574,11 @@ void WriteBlock(const Kernel::Process& process, const VAddr dest_addr, const voi
}
void WriteBlock(const VAddr dest_addr, const void* src_buffer, const size_t size) {
- WriteBlock(*Kernel::g_current_process, dest_addr, src_buffer, size);
+ WriteBlock(*Core::CurrentProcess(), dest_addr, src_buffer, size);
}
-void ZeroBlock(const VAddr dest_addr, const size_t size) {
- const auto& process = *Kernel::g_current_process;
-
+void ZeroBlock(const Kernel::Process& process, const VAddr dest_addr, const size_t size) {
+ auto& page_table = process.vm_manager.page_table;
size_t remaining_size = size;
size_t page_index = dest_addr >> PAGE_BITS;
size_t page_offset = dest_addr & PAGE_MASK;
@@ -448,27 +586,30 @@ void ZeroBlock(const VAddr dest_addr, const size_t size) {
static const std::array<u8, PAGE_SIZE> zeros = {};
while (remaining_size > 0) {
- const size_t copy_amount = std::min<size_t>(PAGE_SIZE - page_offset, remaining_size);
+ const size_t copy_amount =
+ std::min(static_cast<size_t>(PAGE_SIZE) - page_offset, remaining_size);
const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset);
- switch (current_page_table->attributes[page_index]) {
- case PageType::Unmapped:
- LOG_ERROR(HW_Memory,
- "unmapped ZeroBlock @ 0x%016" PRIX64 " (start address = 0x%016" PRIX64
- ", size = %zu)",
- current_vaddr, dest_addr, size);
+ switch (page_table.attributes[page_index]) {
+ case PageType::Unmapped: {
+ NGLOG_ERROR(HW_Memory,
+ "Unmapped ZeroBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})",
+ current_vaddr, dest_addr, size);
break;
- case PageType::Special:
- if (WriteSpecialBlock(process, current_vaddr, zeros.data(), copy_amount))
- break;
- [[fallthrough]];
+ }
case PageType::Memory: {
- DEBUG_ASSERT(current_page_table->pointers[page_index]);
+ DEBUG_ASSERT(page_table.pointers[page_index]);
- u8* dest_ptr = current_page_table->pointers[page_index] + page_offset;
+ 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);
+ break;
+ }
default:
UNREACHABLE();
}
@@ -479,37 +620,35 @@ void ZeroBlock(const VAddr dest_addr, const size_t size) {
}
}
-void CopyBlock(VAddr dest_addr, VAddr src_addr, const size_t size) {
- const auto& process = *Kernel::g_current_process;
-
+void CopyBlock(const Kernel::Process& process, VAddr dest_addr, VAddr src_addr, const size_t size) {
+ auto& page_table = process.vm_manager.page_table;
size_t remaining_size = size;
size_t page_index = src_addr >> PAGE_BITS;
size_t page_offset = src_addr & PAGE_MASK;
while (remaining_size > 0) {
- const size_t copy_amount = std::min<size_t>(PAGE_SIZE - page_offset, remaining_size);
+ const size_t copy_amount =
+ std::min(static_cast<size_t>(PAGE_SIZE) - page_offset, remaining_size);
const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset);
- switch (current_page_table->attributes[page_index]) {
- case PageType::Unmapped:
- LOG_ERROR(HW_Memory,
- "unmapped CopyBlock @ 0x%016" PRIX64 " (start address = 0x%016" PRIX64
- ", size = %zu)",
- current_vaddr, src_addr, size);
- ZeroBlock(dest_addr, copy_amount);
+ switch (page_table.attributes[page_index]) {
+ case PageType::Unmapped: {
+ NGLOG_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::Special: {
- std::vector<u8> buffer(copy_amount);
- if (ReadSpecialBlock(process, current_vaddr, buffer.data(), buffer.size())) {
- WriteBlock(dest_addr, buffer.data(), buffer.size());
- break;
- }
- [[fallthrough]];
}
case PageType::Memory: {
- DEBUG_ASSERT(current_page_table->pointers[page_index]);
- const u8* src_ptr = current_page_table->pointers[page_index] + page_offset;
- WriteBlock(dest_addr, src_ptr, copy_amount);
+ 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);
break;
}
default:
@@ -524,76 +663,8 @@ void CopyBlock(VAddr dest_addr, VAddr src_addr, const size_t size) {
}
}
-template <>
-boost::optional<u8> ReadSpecial<u8>(VAddr addr) {
- const PageTable& page_table = Kernel::g_current_process->vm_manager.page_table;
- for (const auto& handler : GetSpecialHandlers(page_table, addr, sizeof(u8)))
- if (auto result = handler->Read8(addr))
- return *result;
- return {};
-}
-
-template <>
-boost::optional<u16> ReadSpecial<u16>(VAddr addr) {
- const PageTable& page_table = Kernel::g_current_process->vm_manager.page_table;
- for (const auto& handler : GetSpecialHandlers(page_table, addr, sizeof(u16)))
- if (auto result = handler->Read16(addr))
- return *result;
- return {};
-}
-
-template <>
-boost::optional<u32> ReadSpecial<u32>(VAddr addr) {
- const PageTable& page_table = Kernel::g_current_process->vm_manager.page_table;
- for (const auto& handler : GetSpecialHandlers(page_table, addr, sizeof(u32)))
- if (auto result = handler->Read32(addr))
- return *result;
- return {};
-}
-
-template <>
-boost::optional<u64> ReadSpecial<u64>(VAddr addr) {
- const PageTable& page_table = Kernel::g_current_process->vm_manager.page_table;
- for (const auto& handler : GetSpecialHandlers(page_table, addr, sizeof(u64)))
- if (auto result = handler->Read64(addr))
- return *result;
- return {};
-}
-
-template <>
-bool WriteSpecial<u8>(VAddr addr, const u8 data) {
- const PageTable& page_table = Kernel::g_current_process->vm_manager.page_table;
- for (const auto& handler : GetSpecialHandlers(page_table, addr, sizeof(u8)))
- if (handler->Write8(addr, data))
- return true;
- return false;
-}
-
-template <>
-bool WriteSpecial<u16>(VAddr addr, const u16 data) {
- const PageTable& page_table = Kernel::g_current_process->vm_manager.page_table;
- for (const auto& handler : GetSpecialHandlers(page_table, addr, sizeof(u16)))
- if (handler->Write16(addr, data))
- return true;
- return false;
-}
-
-template <>
-bool WriteSpecial<u32>(VAddr addr, const u32 data) {
- const PageTable& page_table = Kernel::g_current_process->vm_manager.page_table;
- for (const auto& handler : GetSpecialHandlers(page_table, addr, sizeof(u32)))
- if (handler->Write32(addr, data))
- return true;
- return false;
-}
-
-template <>
-bool WriteSpecial<u64>(VAddr addr, const u64 data) {
- const PageTable& page_table = Kernel::g_current_process->vm_manager.page_table;
- for (const auto& handler : GetSpecialHandlers(page_table, addr, sizeof(u64)))
- if (handler->Write64(addr, data))
- return true;
- return false;
+void CopyBlock(VAddr dest_addr, VAddr src_addr, size_t size) {
+ CopyBlock(*Core::CurrentProcess(), dest_addr, src_addr, size);
}
boost::optional<PAddr> TryVirtualToPhysicalAddress(const VAddr addr) {
@@ -609,8 +680,6 @@ boost::optional<PAddr> TryVirtualToPhysicalAddress(const VAddr addr) {
return addr - DSP_RAM_VADDR + DSP_RAM_PADDR;
} else if (addr >= IO_AREA_VADDR && addr < IO_AREA_VADDR_END) {
return addr - IO_AREA_VADDR + IO_AREA_PADDR;
- } else if (addr >= N3DS_EXTRA_RAM_VADDR && addr < N3DS_EXTRA_RAM_VADDR_END) {
- return addr - N3DS_EXTRA_RAM_VADDR + N3DS_EXTRA_RAM_PADDR;
}
return boost::none;
@@ -619,7 +688,7 @@ boost::optional<PAddr> TryVirtualToPhysicalAddress(const VAddr addr) {
PAddr VirtualToPhysicalAddress(const VAddr addr) {
auto paddr = TryVirtualToPhysicalAddress(addr);
if (!paddr) {
- LOG_ERROR(HW_Memory, "Unknown virtual address @ 0x%016" PRIX64, addr);
+ NGLOG_ERROR(HW_Memory, "Unknown virtual address @ 0x{:016X}", addr);
// To help with debugging, set bit on address so that it's obviously invalid.
return addr | 0x80000000;
}
@@ -632,13 +701,11 @@ boost::optional<VAddr> PhysicalToVirtualAddress(const PAddr addr) {
} else if (addr >= VRAM_PADDR && addr < VRAM_PADDR_END) {
return addr - VRAM_PADDR + VRAM_VADDR;
} else if (addr >= FCRAM_PADDR && addr < FCRAM_PADDR_END) {
- return addr - FCRAM_PADDR + Kernel::g_current_process->GetLinearHeapAreaAddress();
+ return addr - FCRAM_PADDR + Core::CurrentProcess()->GetLinearHeapAreaAddress();
} else if (addr >= DSP_RAM_PADDR && addr < DSP_RAM_PADDR_END) {
return addr - DSP_RAM_PADDR + DSP_RAM_VADDR;
} else if (addr >= IO_AREA_PADDR && addr < IO_AREA_PADDR_END) {
return addr - IO_AREA_PADDR + IO_AREA_VADDR;
- } else if (addr >= N3DS_EXTRA_RAM_PADDR && addr < N3DS_EXTRA_RAM_PADDR_END) {
- return addr - N3DS_EXTRA_RAM_PADDR + N3DS_EXTRA_RAM_VADDR;
}
return boost::none;
diff --git a/src/core/memory.h b/src/core/memory.h
index f3ace7a98..3f56a2c6a 100644
--- a/src/core/memory.h
+++ b/src/core/memory.h
@@ -14,6 +14,7 @@
#include <boost/optional.hpp>
#include "common/common_types.h"
#include "core/memory_hook.h"
+#include "video_core/memory_manager.h"
namespace Kernel {
class Process;
@@ -36,7 +37,10 @@ enum class PageType : u8 {
Unmapped,
/// Page is mapped to regular memory. This is the only type you can get pointers to.
Memory,
- /// Page is mapped to a memory hook, which intercepts read and write requests.
+ /// 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,
};
@@ -98,12 +102,6 @@ enum : PAddr {
VRAM_SIZE = 0x00600000, ///< VRAM size (6MB)
VRAM_PADDR_END = VRAM_PADDR + VRAM_SIZE,
- /// New 3DS additional memory. Supposedly faster than regular FCRAM. Part of it can be used by
- /// applications and system modules if mapped via the ExHeader.
- N3DS_EXTRA_RAM_PADDR = 0x1F000000,
- N3DS_EXTRA_RAM_SIZE = 0x00400000, ///< New 3DS additional memory size (4MB)
- N3DS_EXTRA_RAM_PADDR_END = N3DS_EXTRA_RAM_PADDR + N3DS_EXTRA_RAM_SIZE,
-
/// DSP memory
DSP_RAM_PADDR = 0x1FF00000,
DSP_RAM_SIZE = 0x00080000, ///< DSP memory size (512KB)
@@ -119,7 +117,6 @@ enum : PAddr {
FCRAM_SIZE = 0x08000000, ///< FCRAM size on the Old 3DS (128MB)
FCRAM_N3DS_SIZE = 0x10000000, ///< FCRAM size on the New 3DS (256MB)
FCRAM_PADDR_END = FCRAM_PADDR + FCRAM_SIZE,
- FCRAM_N3DS_PADDR_END = FCRAM_PADDR + FCRAM_N3DS_SIZE,
};
/// Virtual user-space memory regions
@@ -129,31 +126,12 @@ enum : VAddr {
PROCESS_IMAGE_MAX_SIZE = 0x08000000,
PROCESS_IMAGE_VADDR_END = PROCESS_IMAGE_VADDR + PROCESS_IMAGE_MAX_SIZE,
- /// Area where IPC buffers are mapped onto.
- IPC_MAPPING_VADDR = 0x04000000,
- IPC_MAPPING_SIZE = 0x04000000,
- IPC_MAPPING_VADDR_END = IPC_MAPPING_VADDR + IPC_MAPPING_SIZE,
-
- /// Application heap (includes stack).
- HEAP_VADDR = 0x108000000,
- HEAP_SIZE = 0xF0000000,
- HEAP_VADDR_END = HEAP_VADDR + HEAP_SIZE,
-
- /// Area where shared memory buffers are mapped onto.
- SHARED_MEMORY_VADDR = 0x10000000,
- SHARED_MEMORY_SIZE = 0x04000000,
- SHARED_MEMORY_VADDR_END = SHARED_MEMORY_VADDR + SHARED_MEMORY_SIZE,
-
/// Maps 1:1 to an offset in FCRAM. Used for HW allocations that need to be linear in physical
/// memory.
LINEAR_HEAP_VADDR = 0x14000000,
LINEAR_HEAP_SIZE = 0x08000000,
LINEAR_HEAP_VADDR_END = LINEAR_HEAP_VADDR + LINEAR_HEAP_SIZE,
- /// Maps 1:1 to New 3DS additional memory
- N3DS_EXTRA_RAM_VADDR = 0x1E800000,
- N3DS_EXTRA_RAM_VADDR_END = N3DS_EXTRA_RAM_VADDR + N3DS_EXTRA_RAM_SIZE,
-
/// Maps 1:1 to the IO register area.
IO_AREA_VADDR = 0x1EC00000,
IO_AREA_VADDR_END = IO_AREA_VADDR + IO_AREA_SIZE,
@@ -176,14 +154,40 @@ enum : VAddr {
SHARED_PAGE_SIZE = 0x00001000,
SHARED_PAGE_VADDR_END = SHARED_PAGE_VADDR + SHARED_PAGE_SIZE,
- /// Area where TLS (Thread-Local Storage) buffers are allocated.
- TLS_AREA_VADDR = 0x228000000,
- TLS_ENTRY_SIZE = 0x200,
-
/// Equivalent to LINEAR_HEAP_VADDR, but expanded to cover the extra memory in the New 3DS.
NEW_LINEAR_HEAP_VADDR = 0x30000000,
NEW_LINEAR_HEAP_SIZE = 0x10000000,
NEW_LINEAR_HEAP_VADDR_END = NEW_LINEAR_HEAP_VADDR + NEW_LINEAR_HEAP_SIZE,
+
+ /// Area where TLS (Thread-Local Storage) buffers are allocated.
+ TLS_AREA_VADDR = NEW_LINEAR_HEAP_VADDR_END,
+ TLS_ENTRY_SIZE = 0x200,
+ TLS_AREA_SIZE = 0x10000000,
+ TLS_AREA_VADDR_END = TLS_AREA_VADDR + TLS_AREA_SIZE,
+
+ /// Application stack
+ STACK_AREA_VADDR = TLS_AREA_VADDR_END,
+ STACK_AREA_SIZE = 0x10000000,
+ STACK_AREA_VADDR_END = STACK_AREA_VADDR + STACK_AREA_SIZE,
+ DEFAULT_STACK_SIZE = 0x100000,
+
+ /// Application heap
+ /// Size is confirmed to be a static value on fw 3.0.0
+ HEAP_VADDR = 0x108000000,
+ HEAP_SIZE = 0x180000000,
+ HEAP_VADDR_END = HEAP_VADDR + HEAP_SIZE,
+
+ /// New map region
+ /// Size is confirmed to be a static value on fw 3.0.0
+ NEW_MAP_REGION_VADDR = HEAP_VADDR_END,
+ NEW_MAP_REGION_SIZE = 0x80000000,
+ NEW_MAP_REGION_VADDR_END = NEW_MAP_REGION_VADDR + NEW_MAP_REGION_SIZE,
+
+ /// Map region
+ /// Size is confirmed to be a static value on fw 3.0.0
+ MAP_REGION_VADDR = NEW_MAP_REGION_VADDR_END,
+ MAP_REGION_SIZE = 0x1000000000,
+ MAP_REGION_VADDR_END = MAP_REGION_VADDR + MAP_REGION_SIZE,
};
/// Currently active page table
@@ -243,4 +247,24 @@ boost::optional<VAddr> PhysicalToVirtualAddress(PAddr addr);
*/
u8* GetPhysicalPointer(PAddr address);
+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(Tegra::GPUVAddr start, 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_hook.cpp b/src/core/memory_hook.cpp
new file mode 100644
index 000000000..c61c6c1fb
--- /dev/null
+++ b/src/core/memory_hook.cpp
@@ -0,0 +1,11 @@
+// Copyright 2018 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/memory_hook.h"
+
+namespace Memory {
+
+MemoryHook::~MemoryHook() = default;
+
+} // namespace Memory
diff --git a/src/core/memory_hook.h b/src/core/memory_hook.h
index feebd850a..e8ea19333 100644
--- a/src/core/memory_hook.h
+++ b/src/core/memory_hook.h
@@ -23,7 +23,7 @@ namespace Memory {
*/
class MemoryHook {
public:
- virtual ~MemoryHook() = default;
+ virtual ~MemoryHook();
virtual boost::optional<bool> IsValidAddress(VAddr addr) = 0;
diff --git a/src/core/perf_stats.cpp b/src/core/perf_stats.cpp
index ad3b56fcc..5f53b16d3 100644
--- a/src/core/perf_stats.cpp
+++ b/src/core/perf_stats.cpp
@@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <algorithm>
#include <chrono>
#include <mutex>
#include <thread>
@@ -87,7 +88,7 @@ void FrameLimiter::DoFrameLimiting(u64 current_system_time_us) {
frame_limiting_delta_err += microseconds(current_system_time_us - previous_system_time_us);
frame_limiting_delta_err -= duration_cast<microseconds>(now - previous_walltime);
frame_limiting_delta_err =
- MathUtil::Clamp(frame_limiting_delta_err, -MAX_LAG_TIME_US, MAX_LAG_TIME_US);
+ std::clamp(frame_limiting_delta_err, -MAX_LAG_TIME_US, MAX_LAG_TIME_US);
if (frame_limiting_delta_err > microseconds::zero()) {
std::this_thread::sleep_for(frame_limiting_delta_err);
diff --git a/src/core/settings.h b/src/core/settings.h
index 6f8cd0f03..a7f1e5fa0 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -99,18 +99,20 @@ enum Values {
NumAnalogs,
};
+constexpr int STICK_HID_BEGIN = LStick;
+constexpr int STICK_HID_END = NumAnalogs;
+constexpr int NUM_STICKS_HID = NumAnalogs;
+
static const std::array<const char*, NumAnalogs> mapping = {{
"lstick",
"rstick",
}};
} // namespace NativeAnalog
-enum class CpuCore {
- Unicorn,
- Dynarmic,
-};
-
struct Values {
+ // System
+ bool use_docked_mode;
+
// Controls
std::array<std::string, NativeButton::NumButtons> buttons;
std::array<std::string, NativeAnalog::NumAnalogs> analogs;
@@ -118,7 +120,8 @@ struct Values {
std::string touch_device;
// Core
- CpuCore cpu_core;
+ bool use_cpu_jit;
+ bool use_multi_core;
// Data Storage
bool use_virtual_sd;
diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp
index bea05a09b..a60aa1143 100644
--- a/src/core/telemetry_session.cpp
+++ b/src/core/telemetry_session.cpp
@@ -42,14 +42,14 @@ u64 GetTelemetryId() {
if (FileUtil::Exists(filename)) {
FileUtil::IOFile file(filename, "rb");
if (!file.IsOpen()) {
- LOG_ERROR(Core, "failed to open telemetry_id: %s", filename.c_str());
+ NGLOG_ERROR(Core, "failed to open telemetry_id: {}", filename);
return {};
}
file.ReadBytes(&telemetry_id, sizeof(u64));
} else {
FileUtil::IOFile file(filename, "wb");
if (!file.IsOpen()) {
- LOG_ERROR(Core, "failed to open telemetry_id: %s", filename.c_str());
+ NGLOG_ERROR(Core, "failed to open telemetry_id: {}", filename);
return {};
}
telemetry_id = GenerateTelemetryId();
@@ -65,7 +65,7 @@ u64 RegenerateTelemetryId() {
FileUtil::IOFile file(filename, "wb");
if (!file.IsOpen()) {
- LOG_ERROR(Core, "failed to open telemetry_id: %s", filename.c_str());
+ NGLOG_ERROR(Core, "failed to open telemetry_id: {}", filename);
return {};
}
file.WriteBytes(&new_telemetry_id, sizeof(u64));
@@ -87,8 +87,8 @@ TelemetrySession::TelemetrySession() {
#ifdef ENABLE_WEB_SERVICE
if (Settings::values.enable_telemetry) {
backend = std::make_unique<WebService::TelemetryJson>(
- Settings::values.telemetry_endpoint_url, Settings::values.citra_username,
- Settings::values.citra_token);
+ Settings::values.telemetry_endpoint_url, Settings::values.yuzu_username,
+ Settings::values.yuzu_token);
} else {
backend = std::make_unique<Telemetry::NullVisitor>();
}
@@ -154,12 +154,15 @@ TelemetrySession::TelemetrySession() {
#endif
// Log user configuration information
- AddField(Telemetry::FieldType::UserConfig, "Core_CpuCore",
- static_cast<int>(Settings::values.cpu_core));
+ AddField(Telemetry::FieldType::UserConfig, "Core_UseCpuJit", Settings::values.use_cpu_jit);
+ AddField(Telemetry::FieldType::UserConfig, "Core_UseMultiCore",
+ Settings::values.use_multi_core);
AddField(Telemetry::FieldType::UserConfig, "Renderer_ResolutionFactor",
Settings::values.resolution_factor);
AddField(Telemetry::FieldType::UserConfig, "Renderer_ToggleFramelimit",
Settings::values.toggle_framelimit);
+ AddField(Telemetry::FieldType::UserConfig, "System_UseDockedMode",
+ Settings::values.use_docked_mode);
}
TelemetrySession::~TelemetrySession() {
diff --git a/src/core/telemetry_session.h b/src/core/telemetry_session.h
index 550c6ea2d..dbc4f8bd4 100644
--- a/src/core/telemetry_session.h
+++ b/src/core/telemetry_session.h
@@ -50,8 +50,8 @@ u64 RegenerateTelemetryId();
/**
* Verifies the username and token.
- * @param username Citra username to use for authentication.
- * @param token Citra token to use for authentication.
+ * @param username yuzu username to use for authentication.
+ * @param token yuzu token to use for authentication.
* @param func A function that gets exectued when the verification is finished
* @returns Future with bool indicating whether the verification succeeded
*/
diff --git a/src/core/tracer/recorder.cpp b/src/core/tracer/recorder.cpp
index f3b0d6a8f..2f848c994 100644
--- a/src/core/tracer/recorder.cpp
+++ b/src/core/tracer/recorder.cpp
@@ -159,7 +159,7 @@ void Recorder::Finish(const std::string& filename) {
throw "Failed to write stream element";
}
} catch (const char* str) {
- LOG_ERROR(HW_GPU, "Writing CiTrace file failed: %s", str);
+ NGLOG_ERROR(HW_GPU, "Writing CiTrace file failed: {}", str);
}
}
diff --git a/src/input_common/motion_emu.cpp b/src/input_common/motion_emu.cpp
index 59a035e70..caffe48cb 100644
--- a/src/input_common/motion_emu.cpp
+++ b/src/input_common/motion_emu.cpp
@@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <algorithm>
#include <chrono>
#include <mutex>
#include <thread>
@@ -43,8 +44,8 @@ public:
tilt_angle = 0;
} else {
tilt_direction = mouse_move.Cast<float>();
- tilt_angle = MathUtil::Clamp(tilt_direction.Normalize() * sensitivity, 0.0f,
- MathUtil::PI * 0.5f);
+ tilt_angle =
+ std::clamp(tilt_direction.Normalize() * sensitivity, 0.0f, MathUtil::PI * 0.5f);
}
}
}
diff --git a/src/input_common/sdl/sdl.cpp b/src/input_common/sdl/sdl.cpp
index 3b87d6b65..231a0f7af 100644
--- a/src/input_common/sdl/sdl.cpp
+++ b/src/input_common/sdl/sdl.cpp
@@ -32,7 +32,7 @@ public:
explicit SDLJoystick(int joystick_index)
: joystick{SDL_JoystickOpen(joystick_index), SDL_JoystickClose} {
if (!joystick) {
- LOG_ERROR(Input, "failed to open joystick %d", joystick_index);
+ NGLOG_ERROR(Input, "failed to open joystick {}", joystick_index);
}
}
@@ -204,7 +204,7 @@ public:
trigger_if_greater = false;
} else {
trigger_if_greater = true;
- LOG_ERROR(Input, "Unknown direction %s", direction_name.c_str());
+ NGLOG_ERROR(Input, "Unknown direction '{}'", direction_name);
}
return std::make_unique<SDLAxisButton>(GetJoystick(joystick_index), axis, threshold,
trigger_if_greater);
@@ -235,7 +235,7 @@ public:
void Init() {
if (SDL_Init(SDL_INIT_JOYSTICK) < 0) {
- LOG_CRITICAL(Input, "SDL_Init(SDL_INIT_JOYSTICK) failed with: %s", SDL_GetError());
+ NGLOG_CRITICAL(Input, "SDL_Init(SDL_INIT_JOYSTICK) failed with: {}", SDL_GetError());
} else {
using namespace Input;
RegisterFactory<ButtonDevice>("sdl", std::make_shared<SDLButtonFactory>());
diff --git a/src/tests/core/arm/arm_test_common.cpp b/src/tests/core/arm/arm_test_common.cpp
index 88bbbc95c..7f9f27e19 100644
--- a/src/tests/core/arm/arm_test_common.cpp
+++ b/src/tests/core/arm/arm_test_common.cpp
@@ -15,8 +15,8 @@ static Memory::PageTable* page_table = nullptr;
TestEnvironment::TestEnvironment(bool mutable_memory_)
: mutable_memory(mutable_memory_), test_memory(std::make_shared<TestMemory>(this)) {
- Kernel::g_current_process = Kernel::Process::Create("");
- page_table = &Kernel::g_current_process->vm_manager.page_table;
+ Core::CurrentProcess() = Kernel::Process::Create("");
+ page_table = &Core::CurrentProcess()->vm_manager.page_table;
page_table->pointers.fill(nullptr);
page_table->special_regions.clear();
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index ed87f8ff1..6e193e7e1 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -1,24 +1,49 @@
add_library(video_core STATIC
command_processor.cpp
command_processor.h
+ debug_utils/debug_utils.cpp
+ debug_utils/debug_utils.h
engines/fermi_2d.cpp
engines/fermi_2d.h
engines/maxwell_3d.cpp
engines/maxwell_3d.h
engines/maxwell_compute.cpp
engines/maxwell_compute.h
+ engines/maxwell_dma.cpp
+ engines/maxwell_dma.h
+ engines/shader_bytecode.h
+ gpu.cpp
gpu.h
+ macro_interpreter.cpp
+ macro_interpreter.h
memory_manager.cpp
memory_manager.h
+ rasterizer_interface.h
renderer_base.cpp
renderer_base.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.h
+ renderer_opengl/gl_shader_decompiler.cpp
+ renderer_opengl/gl_shader_decompiler.h
+ renderer_opengl/gl_shader_gen.cpp
+ renderer_opengl/gl_shader_gen.h
+ renderer_opengl/gl_shader_manager.cpp
+ renderer_opengl/gl_shader_manager.h
renderer_opengl/gl_shader_util.cpp
renderer_opengl/gl_shader_util.h
renderer_opengl/gl_state.cpp
renderer_opengl/gl_state.h
+ renderer_opengl/gl_stream_buffer.cpp
+ renderer_opengl/gl_stream_buffer.h
+ renderer_opengl/maxwell_to_gl.h
renderer_opengl/renderer_opengl.cpp
renderer_opengl/renderer_opengl.h
+ textures/decoders.cpp
+ textures/decoders.h
+ textures/texture.h
utils.h
video_core.cpp
video_core.h
diff --git a/src/video_core/command_processor.cpp b/src/video_core/command_processor.cpp
index 21d672085..cec9cb9f3 100644
--- a/src/video_core/command_processor.cpp
+++ b/src/video_core/command_processor.cpp
@@ -16,6 +16,7 @@
#include "video_core/engines/fermi_2d.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/engines/maxwell_compute.h"
+#include "video_core/engines/maxwell_dma.h"
#include "video_core/gpu.h"
#include "video_core/renderer_base.h"
#include "video_core/video_core.h"
@@ -24,24 +25,25 @@ namespace Tegra {
enum class BufferMethods {
BindObject = 0,
- CountBufferMethods = 0x100,
+ CountBufferMethods = 0x40,
};
-void GPU::WriteReg(u32 method, u32 subchannel, u32 value) {
- LOG_WARNING(HW_GPU, "Processing method %08X on subchannel %u value %08X", method, subchannel,
- value);
+void GPU::WriteReg(u32 method, u32 subchannel, u32 value, u32 remaining_params) {
+ NGLOG_WARNING(HW_GPU,
+ "Processing method {:08X} on subchannel {} value "
+ "{:08X} remaining params {}",
+ method, subchannel, value, remaining_params);
if (method == static_cast<u32>(BufferMethods::BindObject)) {
// Bind the current subchannel to the desired engine id.
- LOG_DEBUG(HW_GPU, "Binding subchannel %u to engine %u", subchannel, value);
- ASSERT(bound_engines.find(subchannel) == bound_engines.end());
+ NGLOG_DEBUG(HW_GPU, "Binding subchannel {} to engine {}", subchannel, value);
bound_engines[subchannel] = static_cast<EngineID>(value);
return;
}
if (method < static_cast<u32>(BufferMethods::CountBufferMethods)) {
// TODO(Subv): Research and implement these methods.
- LOG_ERROR(HW_GPU, "Special buffer methods other than Bind are not implemented");
+ NGLOG_ERROR(HW_GPU, "Special buffer methods other than Bind are not implemented");
return;
}
@@ -54,22 +56,23 @@ void GPU::WriteReg(u32 method, u32 subchannel, u32 value) {
fermi_2d->WriteReg(method, value);
break;
case EngineID::MAXWELL_B:
- maxwell_3d->WriteReg(method, value);
+ maxwell_3d->WriteReg(method, value, remaining_params);
break;
case EngineID::MAXWELL_COMPUTE_B:
maxwell_compute->WriteReg(method, value);
break;
+ case EngineID::MAXWELL_DMA_COPY_A:
+ maxwell_dma->WriteReg(method, value);
+ break;
default:
- UNIMPLEMENTED();
+ UNIMPLEMENTED_MSG("Unimplemented engine");
}
}
void GPU::ProcessCommandList(GPUVAddr address, u32 size) {
- // TODO(Subv): PhysicalToVirtualAddress is a misnomer, it converts a GPU VAddr into an
- // application VAddr.
- const VAddr head_address = memory_manager->PhysicalToVirtualAddress(address);
- VAddr current_addr = head_address;
- while (current_addr < head_address + size * sizeof(CommandHeader)) {
+ const boost::optional<VAddr> head_address = memory_manager->GpuToCpuAddress(address);
+ VAddr current_addr = *head_address;
+ while (current_addr < *head_address + size * sizeof(CommandHeader)) {
const CommandHeader header = {Memory::Read32(current_addr)};
current_addr += sizeof(u32);
@@ -78,7 +81,8 @@ void GPU::ProcessCommandList(GPUVAddr address, u32 size) {
case SubmissionMode::Increasing: {
// Increase the method value with each argument.
for (unsigned i = 0; i < header.arg_count; ++i) {
- WriteReg(header.method + i, header.subchannel, Memory::Read32(current_addr));
+ WriteReg(header.method + i, header.subchannel, Memory::Read32(current_addr),
+ header.arg_count - i - 1);
current_addr += sizeof(u32);
}
break;
@@ -87,27 +91,31 @@ void GPU::ProcessCommandList(GPUVAddr address, u32 size) {
case SubmissionMode::NonIncreasing: {
// Use the same method value for all arguments.
for (unsigned i = 0; i < header.arg_count; ++i) {
- WriteReg(header.method, header.subchannel, Memory::Read32(current_addr));
+ WriteReg(header.method, header.subchannel, Memory::Read32(current_addr),
+ header.arg_count - i - 1);
current_addr += sizeof(u32);
}
break;
}
case SubmissionMode::IncreaseOnce: {
ASSERT(header.arg_count.Value() >= 1);
+
// Use the original method for the first argument and then the next method for all other
// arguments.
- WriteReg(header.method, header.subchannel, Memory::Read32(current_addr));
+ WriteReg(header.method, header.subchannel, Memory::Read32(current_addr),
+ header.arg_count - 1);
current_addr += sizeof(u32);
- // Use the same method value for all arguments.
+
for (unsigned i = 1; i < header.arg_count; ++i) {
- WriteReg(header.method + 1, header.subchannel, Memory::Read32(current_addr));
+ WriteReg(header.method + 1, header.subchannel, Memory::Read32(current_addr),
+ header.arg_count - i - 1);
current_addr += sizeof(u32);
}
break;
}
case SubmissionMode::Inline: {
// The register value is stored in the bits 16-28 as an immediate
- WriteReg(header.method, header.subchannel, header.inline_data);
+ WriteReg(header.method, header.subchannel, header.inline_data, 0);
break;
}
default:
diff --git a/src/video_core/command_processor.h b/src/video_core/command_processor.h
index b511bfcf7..f7214ffec 100644
--- a/src/video_core/command_processor.h
+++ b/src/video_core/command_processor.h
@@ -34,6 +34,4 @@ static_assert(std::is_standard_layout<CommandHeader>::value == true,
"CommandHeader does not use standard layout");
static_assert(sizeof(CommandHeader) == sizeof(u32), "CommandHeader has incorrect size!");
-void ProcessCommandList(VAddr address, u32 size);
-
} // namespace Tegra
diff --git a/src/video_core/debug_utils/debug_utils.cpp b/src/video_core/debug_utils/debug_utils.cpp
new file mode 100644
index 000000000..22d44aab2
--- /dev/null
+++ b/src/video_core/debug_utils/debug_utils.cpp
@@ -0,0 +1,64 @@
+// Copyright 2014 Citra Emulator Project
+// Licensed under GPLv2
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <condition_variable>
+#include <cstdint>
+#include <cstring>
+#include <fstream>
+#include <map>
+#include <mutex>
+#include <string>
+
+#include "common/assert.h"
+#include "common/bit_field.h"
+#include "common/color.h"
+#include "common/common_types.h"
+#include "common/file_util.h"
+#include "common/logging/log.h"
+#include "common/math_util.h"
+#include "common/vector_math.h"
+#include "video_core/debug_utils/debug_utils.h"
+
+namespace Tegra {
+
+void DebugContext::DoOnEvent(Event event, void* data) {
+ {
+ std::unique_lock<std::mutex> lock(breakpoint_mutex);
+
+ // TODO(Subv): Commit the rasterizer's caches so framebuffers, render targets, etc. will
+ // show on debug widgets
+
+ // TODO: Should stop the CPU thread here once we multithread emulation.
+
+ active_breakpoint = event;
+ at_breakpoint = true;
+
+ // Tell all observers that we hit a breakpoint
+ for (auto& breakpoint_observer : breakpoint_observers) {
+ breakpoint_observer->OnMaxwellBreakPointHit(event, data);
+ }
+
+ // Wait until another thread tells us to Resume()
+ resume_from_breakpoint.wait(lock, [&] { return !at_breakpoint; });
+ }
+}
+
+void DebugContext::Resume() {
+ {
+ std::lock_guard<std::mutex> lock(breakpoint_mutex);
+
+ // Tell all observers that we are about to resume
+ for (auto& breakpoint_observer : breakpoint_observers) {
+ breakpoint_observer->OnMaxwellResume();
+ }
+
+ // Resume the waiting thread (i.e. OnEvent())
+ at_breakpoint = false;
+ }
+
+ resume_from_breakpoint.notify_one();
+}
+
+} // namespace Tegra
diff --git a/src/video_core/debug_utils/debug_utils.h b/src/video_core/debug_utils/debug_utils.h
new file mode 100644
index 000000000..bbba8e380
--- /dev/null
+++ b/src/video_core/debug_utils/debug_utils.h
@@ -0,0 +1,163 @@
+// Copyright 2014 Citra Emulator Project
+// Licensed under GPLv2
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <algorithm>
+#include <array>
+#include <condition_variable>
+#include <iterator>
+#include <list>
+#include <map>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <utility>
+#include <vector>
+#include "common/common_types.h"
+#include "common/vector_math.h"
+
+namespace Tegra {
+
+class DebugContext {
+public:
+ enum class Event {
+ FirstEvent = 0,
+
+ MaxwellCommandLoaded = FirstEvent,
+ MaxwellCommandProcessed,
+ IncomingPrimitiveBatch,
+ FinishedPrimitiveBatch,
+
+ NumEvents
+ };
+
+ /**
+ * Inherit from this class to be notified of events registered to some debug context.
+ * Most importantly this is used for our debugger GUI.
+ *
+ * To implement event handling, override the OnMaxwellBreakPointHit and OnMaxwellResume methods.
+ * @warning All BreakPointObservers need to be on the same thread to guarantee thread-safe state
+ * access
+ * @todo Evaluate an alternative interface, in which there is only one managing observer and
+ * multiple child observers running (by design) on the same thread.
+ */
+ class BreakPointObserver {
+ public:
+ /// Constructs the object such that it observes events of the given DebugContext.
+ BreakPointObserver(std::shared_ptr<DebugContext> debug_context)
+ : context_weak(debug_context) {
+ std::unique_lock<std::mutex> lock(debug_context->breakpoint_mutex);
+ debug_context->breakpoint_observers.push_back(this);
+ }
+
+ virtual ~BreakPointObserver() {
+ auto context = context_weak.lock();
+ if (context) {
+ std::unique_lock<std::mutex> lock(context->breakpoint_mutex);
+ context->breakpoint_observers.remove(this);
+
+ // If we are the last observer to be destroyed, tell the debugger context that
+ // it is free to continue. In particular, this is required for a proper yuzu
+ // shutdown, when the emulation thread is waiting at a breakpoint.
+ if (context->breakpoint_observers.empty())
+ context->Resume();
+ }
+ }
+
+ /**
+ * Action to perform when a breakpoint was reached.
+ * @param event Type of event which triggered the breakpoint
+ * @param data Optional data pointer (if unused, this is a nullptr)
+ * @note This function will perform nothing unless it is overridden in the child class.
+ */
+ virtual void OnMaxwellBreakPointHit(Event event, void* data) {}
+
+ /**
+ * Action to perform when emulation is resumed from a breakpoint.
+ * @note This function will perform nothing unless it is overridden in the child class.
+ */
+ virtual void OnMaxwellResume() {}
+
+ protected:
+ /**
+ * Weak context pointer. This need not be valid, so when requesting a shared_ptr via
+ * context_weak.lock(), always compare the result against nullptr.
+ */
+ std::weak_ptr<DebugContext> context_weak;
+ };
+
+ /**
+ * Simple structure defining a breakpoint state
+ */
+ struct BreakPoint {
+ bool enabled = false;
+ };
+
+ /**
+ * Static constructor used to create a shared_ptr of a DebugContext.
+ */
+ static std::shared_ptr<DebugContext> Construct() {
+ return std::shared_ptr<DebugContext>(new DebugContext);
+ }
+
+ /**
+ * Used by the emulation core when a given event has happened. If a breakpoint has been set
+ * for this event, OnEvent calls the event handlers of the registered breakpoint observers.
+ * The current thread then is halted until Resume() is called from another thread (or until
+ * emulation is stopped).
+ * @param event Event which has happened
+ * @param data Optional data pointer (pass nullptr if unused). Needs to remain valid until
+ * Resume() is called.
+ */
+ void OnEvent(Event event, void* data) {
+ // This check is left in the header to allow the compiler to inline it.
+ if (!breakpoints[(int)event].enabled)
+ return;
+ // For the rest of event handling, call a separate function.
+ DoOnEvent(event, data);
+ }
+
+ void DoOnEvent(Event event, void* data);
+
+ /**
+ * Resume from the current breakpoint.
+ * @warning Calling this from the same thread that OnEvent was called in will cause a deadlock.
+ * Calling from any other thread is safe.
+ */
+ void Resume();
+
+ /**
+ * Delete all set breakpoints and resume emulation.
+ */
+ void ClearBreakpoints() {
+ for (auto& bp : breakpoints) {
+ bp.enabled = false;
+ }
+ Resume();
+ }
+
+ // TODO: Evaluate if access to these members should be hidden behind a public interface.
+ std::array<BreakPoint, (int)Event::NumEvents> breakpoints;
+ Event active_breakpoint;
+ bool at_breakpoint = false;
+
+private:
+ /**
+ * Private default constructor to make sure people always construct this through Construct()
+ * instead.
+ */
+ DebugContext() = default;
+
+ /// Mutex protecting current breakpoint state and the observer list.
+ std::mutex breakpoint_mutex;
+
+ /// Used by OnEvent to wait for resumption.
+ std::condition_variable resume_from_breakpoint;
+
+ /// List of registered observers
+ std::list<BreakPointObserver*> breakpoint_observers;
+};
+
+} // namespace Tegra
diff --git a/src/video_core/engines/fermi_2d.cpp b/src/video_core/engines/fermi_2d.cpp
index 7aab163dc..998b7c843 100644
--- a/src/video_core/engines/fermi_2d.cpp
+++ b/src/video_core/engines/fermi_2d.cpp
@@ -2,12 +2,72 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include "core/memory.h"
#include "video_core/engines/fermi_2d.h"
+#include "video_core/textures/decoders.h"
namespace Tegra {
namespace Engines {
-void Fermi2D::WriteReg(u32 method, u32 value) {}
+Fermi2D::Fermi2D(MemoryManager& memory_manager) : memory_manager(memory_manager) {}
+
+void Fermi2D::WriteReg(u32 method, u32 value) {
+ ASSERT_MSG(method < Regs::NUM_REGS,
+ "Invalid Fermi2D register, increase the size of the Regs structure");
+
+ regs.reg_array[method] = value;
+
+ switch (method) {
+ case FERMI2D_REG_INDEX(trigger): {
+ HandleSurfaceCopy();
+ break;
+ }
+ }
+}
+
+void Fermi2D::HandleSurfaceCopy() {
+ NGLOG_WARNING(HW_GPU, "Requested a surface copy with operation {}",
+ static_cast<u32>(regs.operation));
+
+ const GPUVAddr source = regs.src.Address();
+ const GPUVAddr dest = regs.dst.Address();
+
+ // TODO(Subv): Only same-format and same-size copies are allowed for now.
+ ASSERT(regs.src.format == regs.dst.format);
+ ASSERT(regs.src.width * regs.src.height == regs.dst.width * regs.dst.height);
+
+ // TODO(Subv): Only raw copies are implemented.
+ ASSERT(regs.operation == Regs::Operation::SrcCopy);
+
+ const VAddr source_cpu = *memory_manager.GpuToCpuAddress(source);
+ const VAddr dest_cpu = *memory_manager.GpuToCpuAddress(dest);
+
+ u32 src_bytes_per_pixel = RenderTargetBytesPerPixel(regs.src.format);
+ u32 dst_bytes_per_pixel = RenderTargetBytesPerPixel(regs.dst.format);
+
+ if (regs.src.linear == regs.dst.linear) {
+ // If the input layout and the output layout are the same, just perform a raw copy.
+ ASSERT(regs.src.BlockHeight() == regs.dst.BlockHeight());
+ Memory::CopyBlock(dest_cpu, source_cpu,
+ src_bytes_per_pixel * regs.dst.width * regs.dst.height);
+ return;
+ }
+
+ u8* src_buffer = Memory::GetPointer(source_cpu);
+ u8* dst_buffer = Memory::GetPointer(dest_cpu);
+
+ if (!regs.src.linear && regs.dst.linear) {
+ // If the input is tiled and the output is linear, deswizzle the input and copy it over.
+ Texture::CopySwizzledData(regs.src.width, regs.src.height, src_bytes_per_pixel,
+ dst_bytes_per_pixel, src_buffer, dst_buffer, true,
+ regs.src.BlockHeight());
+ } else {
+ // If the input is linear and the output is tiled, swizzle the input and copy it over.
+ Texture::CopySwizzledData(regs.src.width, regs.src.height, src_bytes_per_pixel,
+ dst_bytes_per_pixel, dst_buffer, src_buffer, false,
+ regs.dst.BlockHeight());
+ }
+}
} // namespace Engines
} // namespace Tegra
diff --git a/src/video_core/engines/fermi_2d.h b/src/video_core/engines/fermi_2d.h
index 8967ddede..70667cb94 100644
--- a/src/video_core/engines/fermi_2d.h
+++ b/src/video_core/engines/fermi_2d.h
@@ -4,19 +4,111 @@
#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/gpu.h"
+#include "video_core/memory_manager.h"
namespace Tegra {
namespace Engines {
+#define FERMI2D_REG_INDEX(field_name) \
+ (offsetof(Tegra::Engines::Fermi2D::Regs, field_name) / sizeof(u32))
+
class Fermi2D final {
public:
- Fermi2D() = default;
+ explicit Fermi2D(MemoryManager& memory_manager);
~Fermi2D() = default;
/// Write the value to the register identified by method.
void WriteReg(u32 method, u32 value);
+
+ struct Regs {
+ static constexpr size_t NUM_REGS = 0x258;
+
+ struct Surface {
+ RenderTargetFormat format;
+ BitField<0, 1, u32> linear;
+ union {
+ BitField<0, 4, u32> block_depth;
+ BitField<4, 4, u32> block_height;
+ BitField<8, 4, u32> block_width;
+ };
+ u32 depth;
+ u32 layer;
+ u32 pitch;
+ u32 width;
+ u32 height;
+ u32 address_high;
+ u32 address_low;
+
+ GPUVAddr Address() const {
+ return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) |
+ address_low);
+ }
+
+ u32 BlockHeight() const {
+ // The block height is stored in log2 format.
+ return 1 << block_height;
+ }
+ };
+ static_assert(sizeof(Surface) == 0x28, "Surface has incorrect size");
+
+ enum class Operation : u32 {
+ SrcCopyAnd = 0,
+ ROPAnd = 1,
+ Blend = 2,
+ SrcCopy = 3,
+ ROP = 4,
+ SrcCopyPremult = 5,
+ BlendPremult = 6,
+ };
+
+ union {
+ struct {
+ INSERT_PADDING_WORDS(0x80);
+
+ Surface dst;
+
+ INSERT_PADDING_WORDS(2);
+
+ Surface src;
+
+ INSERT_PADDING_WORDS(0x15);
+
+ Operation operation;
+
+ INSERT_PADDING_WORDS(0x9);
+
+ // TODO(Subv): This is only a guess.
+ u32 trigger;
+
+ INSERT_PADDING_WORDS(0x1A3);
+ };
+ std::array<u32, NUM_REGS> reg_array;
+ };
+ } regs{};
+
+ MemoryManager& memory_manager;
+
+private:
+ /// Performs the copy from the source surface to the destination surface as configured in the
+ /// registers.
+ void HandleSurfaceCopy();
};
+#define ASSERT_REG_POSITION(field_name, position) \
+ static_assert(offsetof(Fermi2D::Regs, field_name) == position * 4, \
+ "Field " #field_name " has invalid position")
+
+ASSERT_REG_POSITION(dst, 0x80);
+ASSERT_REG_POSITION(src, 0x8C);
+ASSERT_REG_POSITION(operation, 0xAB);
+ASSERT_REG_POSITION(trigger, 0xB5);
+#undef ASSERT_REG_POSITION
+
} // namespace Engines
} // namespace Tegra
diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp
index 9f699399f..86e9dc998 100644
--- a/src/video_core/engines/maxwell_3d.cpp
+++ b/src/video_core/engines/maxwell_3d.cpp
@@ -2,23 +2,130 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <cinttypes>
#include "common/assert.h"
+#include "core/core.h"
+#include "video_core/debug_utils/debug_utils.h"
#include "video_core/engines/maxwell_3d.h"
+#include "video_core/rasterizer_interface.h"
+#include "video_core/renderer_base.h"
+#include "video_core/textures/decoders.h"
+#include "video_core/textures/texture.h"
+#include "video_core/video_core.h"
namespace Tegra {
namespace Engines {
-Maxwell3D::Maxwell3D(MemoryManager& memory_manager) : memory_manager(memory_manager) {}
+/// First register id that is actually a Macro call.
+constexpr u32 MacroRegistersStart = 0xE00;
+
+Maxwell3D::Maxwell3D(MemoryManager& memory_manager)
+ : memory_manager(memory_manager), macro_interpreter(*this) {}
+
+void Maxwell3D::CallMacroMethod(u32 method, std::vector<u32> parameters) {
+ auto macro_code = uploaded_macros.find(method);
+ // The requested macro must have been uploaded already.
+ ASSERT_MSG(macro_code != uploaded_macros.end(), "Macro %08X was not uploaded", method);
+
+ // Reset the current macro and execute it.
+ executing_macro = 0;
+ macro_interpreter.Execute(macro_code->second, std::move(parameters));
+}
+
+void Maxwell3D::WriteReg(u32 method, u32 value, u32 remaining_params) {
+ auto debug_context = Core::System::GetInstance().GetGPUDebugContext();
+
+ // It is an error to write to a register other than the current macro's ARG register before it
+ // has finished execution.
+ if (executing_macro != 0) {
+ ASSERT(method == executing_macro + 1);
+ }
+
+ // Methods after 0xE00 are special, they're actually triggers for some microcode that was
+ // uploaded to the GPU during initialization.
+ if (method >= MacroRegistersStart) {
+ // We're trying to execute a macro
+ if (executing_macro == 0) {
+ // A macro call must begin by writing the macro method's register, not its argument.
+ ASSERT_MSG((method % 2) == 0,
+ "Can't start macro execution by writing to the ARGS register");
+ executing_macro = method;
+ }
+
+ macro_params.push_back(value);
+
+ // Call the macro when there are no more parameters in the command buffer
+ if (remaining_params == 0) {
+ CallMacroMethod(executing_macro, std::move(macro_params));
+ }
+ return;
+ }
-void Maxwell3D::WriteReg(u32 method, u32 value) {
ASSERT_MSG(method < Regs::NUM_REGS,
"Invalid Maxwell3D register, increase the size of the Regs structure");
- regs.reg_array[method] = value;
+ if (debug_context) {
+ debug_context->OnEvent(Tegra::DebugContext::Event::MaxwellCommandLoaded, nullptr);
+ }
-#define MAXWELL3D_REG_INDEX(field_name) (offsetof(Regs, field_name) / sizeof(u32))
+ regs.reg_array[method] = value;
switch (method) {
+ case MAXWELL3D_REG_INDEX(macros.data): {
+ ProcessMacroUpload(value);
+ break;
+ }
+ case MAXWELL3D_REG_INDEX(code_address.code_address_high):
+ case MAXWELL3D_REG_INDEX(code_address.code_address_low): {
+ // Note: For some reason games (like Puyo Puyo Tetris) seem to write 0 to the CODE_ADDRESS
+ // register, we do not currently know if that's intended or a bug, so we assert it lest
+ // stuff breaks in other places (like the shader address calculation).
+ ASSERT_MSG(regs.code_address.CodeAddress() == 0, "Unexpected CODE_ADDRESS register value.");
+ break;
+ }
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data[0]):
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data[1]):
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data[2]):
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data[3]):
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data[4]):
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data[5]):
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data[6]):
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data[7]):
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data[8]):
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data[9]):
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data[10]):
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data[11]):
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data[12]):
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data[13]):
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data[14]):
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data[15]): {
+ ProcessCBData(value);
+ break;
+ }
+ case MAXWELL3D_REG_INDEX(cb_bind[0].raw_config): {
+ ProcessCBBind(Regs::ShaderStage::Vertex);
+ break;
+ }
+ case MAXWELL3D_REG_INDEX(cb_bind[1].raw_config): {
+ ProcessCBBind(Regs::ShaderStage::TesselationControl);
+ break;
+ }
+ case MAXWELL3D_REG_INDEX(cb_bind[2].raw_config): {
+ ProcessCBBind(Regs::ShaderStage::TesselationEval);
+ break;
+ }
+ case MAXWELL3D_REG_INDEX(cb_bind[3].raw_config): {
+ ProcessCBBind(Regs::ShaderStage::Geometry);
+ break;
+ }
+ case MAXWELL3D_REG_INDEX(cb_bind[4].raw_config): {
+ ProcessCBBind(Regs::ShaderStage::Fragment);
+ break;
+ }
+ case MAXWELL3D_REG_INDEX(draw.vertex_end_gl): {
+ DrawArrays();
+ break;
+ }
case MAXWELL3D_REG_INDEX(query.query_get): {
ProcessQueryGet();
break;
@@ -27,25 +134,285 @@ void Maxwell3D::WriteReg(u32 method, u32 value) {
break;
}
-#undef MAXWELL3D_REG_INDEX
+ VideoCore::g_renderer->Rasterizer()->NotifyMaxwellRegisterChanged(method);
+
+ if (debug_context) {
+ debug_context->OnEvent(Tegra::DebugContext::Event::MaxwellCommandProcessed, nullptr);
+ }
+}
+
+void Maxwell3D::ProcessMacroUpload(u32 data) {
+ // Store the uploaded macro code to interpret them when they're called.
+ auto& macro = uploaded_macros[regs.macros.entry * 2 + MacroRegistersStart];
+ macro.push_back(data);
}
void Maxwell3D::ProcessQueryGet() {
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.
- VAddr address = memory_manager.PhysicalToVirtualAddress(sequence_address);
+ boost::optional<VAddr> address = memory_manager.GpuToCpuAddress(sequence_address);
+
+ // TODO(Subv): Support the other query units.
+ ASSERT_MSG(regs.query.query_get.unit == Regs::QueryUnit::Crop,
+ "Units other than CROP are unimplemented");
+
+ u32 value = Memory::Read32(*address);
+ u64 result = 0;
+
+ // TODO(Subv): Support the other query variables
+ switch (regs.query.query_get.select) {
+ case Regs::QuerySelect::Zero:
+ // This seems to actually write the query sequence to the query address.
+ result = regs.query.query_sequence;
+ break;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented query select type {}",
+ static_cast<u32>(regs.query.query_get.select.Value()));
+ }
+
+ // TODO(Subv): Research and implement how query sync conditions work.
+
+ struct LongQueryResult {
+ u64_le value;
+ u64_le timestamp;
+ };
+ static_assert(sizeof(LongQueryResult) == 16, "LongQueryResult has wrong size");
switch (regs.query.query_get.mode) {
- case Regs::QueryMode::Write: {
- // Write the current query sequence to the sequence address.
+ case Regs::QueryMode::Write:
+ case Regs::QueryMode::Write2: {
u32 sequence = regs.query.query_sequence;
- Memory::Write32(address, sequence);
+ if (regs.query.query_get.short_query) {
+ // 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);
+ } 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
+ // wait queues.
+ LongQueryResult query_result{};
+ query_result.value = result;
+ // TODO(Subv): Generate a real GPU timestamp and write it here instead of 0
+ query_result.timestamp = 0;
+ Memory::WriteBlock(*address, &query_result, sizeof(query_result));
+ }
break;
}
default:
- UNIMPLEMENTED_MSG("Query mode %u not implemented", regs.query.query_get.mode.Value());
+ UNIMPLEMENTED_MSG("Query mode {} not implemented",
+ static_cast<u32>(regs.query.query_get.mode.Value()));
+ }
+}
+
+void Maxwell3D::DrawArrays() {
+ NGLOG_DEBUG(HW_GPU, "called, topology={}, count={}",
+ static_cast<u32>(regs.draw.topology.Value()), regs.vertex_buffer.count);
+ ASSERT_MSG(!(regs.index_array.count && regs.vertex_buffer.count), "Both indexed and direct?");
+
+ auto debug_context = Core::System::GetInstance().GetGPUDebugContext();
+
+ if (debug_context) {
+ debug_context->OnEvent(Tegra::DebugContext::Event::IncomingPrimitiveBatch, nullptr);
+ }
+
+ if (debug_context) {
+ debug_context->OnEvent(Tegra::DebugContext::Event::FinishedPrimitiveBatch, nullptr);
+ }
+
+ const bool is_indexed{regs.index_array.count && !regs.vertex_buffer.count};
+ VideoCore::g_renderer->Rasterizer()->AccelerateDrawBatch(is_indexed);
+
+ // TODO(bunnei): Below, we reset vertex count so that we can use these registers to determine if
+ // the game is trying to draw indexed or direct mode. This needs to be verified on HW still -
+ // it's possible that it is incorrect and that there is some other register used to specify the
+ // drawing mode.
+ if (is_indexed) {
+ regs.index_array.count = 0;
+ } else {
+ regs.vertex_buffer.count = 0;
+ }
+}
+
+void Maxwell3D::ProcessCBBind(Regs::ShaderStage stage) {
+ // Bind the buffer currently in CB_ADDRESS to the specified index in the desired shader stage.
+ auto& shader = state.shader_stages[static_cast<size_t>(stage)];
+ auto& bind_data = regs.cb_bind[static_cast<size_t>(stage)];
+
+ 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;
+}
+
+void Maxwell3D::ProcessCBData(u32 value) {
+ // Write the input value to the current const buffer at the current position.
+ GPUVAddr buffer_address = regs.const_buffer.BufferAddress();
+ ASSERT(buffer_address != 0);
+
+ // Don't allow writing past the end of the buffer.
+ ASSERT(regs.const_buffer.cb_pos + sizeof(u32) <= regs.const_buffer.cb_size);
+
+ boost::optional<VAddr> address =
+ memory_manager.GpuToCpuAddress(buffer_address + regs.const_buffer.cb_pos);
+
+ Memory::Write32(*address, value);
+
+ // Increment the current buffer position.
+ regs.const_buffer.cb_pos = regs.const_buffer.cb_pos + 4;
+}
+
+Texture::TICEntry Maxwell3D::GetTICEntry(u32 tic_index) const {
+ GPUVAddr tic_base_address = regs.tic.TICAddress();
+
+ GPUVAddr tic_address_gpu = tic_base_address + tic_index * sizeof(Texture::TICEntry);
+ boost::optional<VAddr> tic_address_cpu = memory_manager.GpuToCpuAddress(tic_address_gpu);
+
+ Texture::TICEntry tic_entry;
+ Memory::ReadBlock(*tic_address_cpu, &tic_entry, sizeof(Texture::TICEntry));
+
+ ASSERT_MSG(tic_entry.header_version == Texture::TICHeaderVersion::BlockLinear ||
+ tic_entry.header_version == Texture::TICHeaderVersion::Pitch,
+ "TIC versions other than BlockLinear or Pitch are unimplemented");
+
+ ASSERT_MSG((tic_entry.texture_type == Texture::TextureType::Texture2D) ||
+ (tic_entry.texture_type == Texture::TextureType::Texture2DNoMipmap),
+ "Texture types other than Texture2D are unimplemented");
+
+ auto r_type = tic_entry.r_type.Value();
+ auto g_type = tic_entry.g_type.Value();
+ auto b_type = tic_entry.b_type.Value();
+ 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);
+ // TODO(Subv): Only UNORM formats are supported for now.
+ ASSERT(r_type == Texture::ComponentType::UNORM);
+
+ return tic_entry;
+}
+
+Texture::TSCEntry Maxwell3D::GetTSCEntry(u32 tsc_index) const {
+ GPUVAddr tsc_base_address = regs.tsc.TSCAddress();
+
+ GPUVAddr tsc_address_gpu = tsc_base_address + tsc_index * sizeof(Texture::TSCEntry);
+ boost::optional<VAddr> tsc_address_cpu = memory_manager.GpuToCpuAddress(tsc_address_gpu);
+
+ Texture::TSCEntry tsc_entry;
+ Memory::ReadBlock(*tsc_address_cpu, &tsc_entry, sizeof(Texture::TSCEntry));
+ return tsc_entry;
+}
+
+std::vector<Texture::FullTextureInfo> Maxwell3D::GetStageTextures(Regs::ShaderStage stage) const {
+ std::vector<Texture::FullTextureInfo> textures;
+
+ auto& fragment_shader = state.shader_stages[static_cast<size_t>(stage)];
+ auto& tex_info_buffer = fragment_shader.const_buffers[regs.tex_cb_index];
+ ASSERT(tex_info_buffer.enabled && tex_info_buffer.address != 0);
+
+ GPUVAddr tic_base_address = regs.tic.TICAddress();
+
+ GPUVAddr tex_info_buffer_end = tex_info_buffer.address + tex_info_buffer.size;
+
+ // Offset into the texture constbuffer where the texture info begins.
+ static constexpr size_t TextureInfoOffset = 0x20;
+
+ for (GPUVAddr current_texture = tex_info_buffer.address + TextureInfoOffset;
+ current_texture < tex_info_buffer_end; current_texture += sizeof(Texture::TextureHandle)) {
+
+ Texture::TextureHandle tex_handle{
+ Memory::Read32(*memory_manager.GpuToCpuAddress(current_texture))};
+
+ Texture::FullTextureInfo tex_info{};
+ // TODO(Subv): Use the shader to determine which textures are actually accessed.
+ tex_info.index = (current_texture - tex_info_buffer.address - TextureInfoOffset) /
+ sizeof(Texture::TextureHandle);
+
+ // Load the TIC data.
+ if (tex_handle.tic_id != 0) {
+ tex_info.enabled = true;
+
+ auto tic_entry = GetTICEntry(tex_handle.tic_id);
+ // TODO(Subv): Workaround for BitField's move constructor being deleted.
+ std::memcpy(&tex_info.tic, &tic_entry, sizeof(tic_entry));
+ }
+
+ // Load the TSC data
+ if (tex_handle.tsc_id != 0) {
+ auto tsc_entry = GetTSCEntry(tex_handle.tsc_id);
+ // TODO(Subv): Workaround for BitField's move constructor being deleted.
+ std::memcpy(&tex_info.tsc, &tsc_entry, sizeof(tsc_entry));
+ }
+
+ if (tex_info.enabled)
+ textures.push_back(tex_info);
+ }
+
+ return textures;
+}
+
+Texture::FullTextureInfo Maxwell3D::GetStageTexture(Regs::ShaderStage stage, size_t offset) const {
+ auto& shader = state.shader_stages[static_cast<size_t>(stage)];
+ auto& tex_info_buffer = shader.const_buffers[regs.tex_cb_index];
+ ASSERT(tex_info_buffer.enabled && tex_info_buffer.address != 0);
+
+ GPUVAddr tex_info_address = tex_info_buffer.address + offset * sizeof(Texture::TextureHandle);
+
+ ASSERT(tex_info_address < tex_info_buffer.address + tex_info_buffer.size);
+
+ boost::optional<VAddr> tex_address_cpu = memory_manager.GpuToCpuAddress(tex_info_address);
+ Texture::TextureHandle tex_handle{Memory::Read32(*tex_address_cpu)};
+
+ Texture::FullTextureInfo tex_info{};
+ tex_info.index = static_cast<u32>(offset);
+
+ // Load the TIC data.
+ if (tex_handle.tic_id != 0) {
+ tex_info.enabled = true;
+
+ auto tic_entry = GetTICEntry(tex_handle.tic_id);
+ // TODO(Subv): Workaround for BitField's move constructor being deleted.
+ std::memcpy(&tex_info.tic, &tic_entry, sizeof(tic_entry));
+ }
+
+ // Load the TSC data
+ if (tex_handle.tsc_id != 0) {
+ auto tsc_entry = GetTSCEntry(tex_handle.tsc_id);
+ // TODO(Subv): Workaround for BitField's move constructor being deleted.
+ std::memcpy(&tex_info.tsc, &tsc_entry, sizeof(tsc_entry));
+ }
+
+ return tex_info;
+}
+
+u32 Maxwell3D::GetRegisterValue(u32 method) const {
+ ASSERT_MSG(method < Regs::NUM_REGS, "Invalid Maxwell3D register");
+ return regs.reg_array[method];
+}
+
+bool Maxwell3D::IsShaderStageEnabled(Regs::ShaderStage stage) const {
+ // The Vertex stage is always enabled.
+ if (stage == Regs::ShaderStage::Vertex)
+ return true;
+
+ switch (stage) {
+ case Regs::ShaderStage::TesselationControl:
+ return regs.shader_config[static_cast<size_t>(Regs::ShaderProgram::TesselationControl)]
+ .enable != 0;
+ case Regs::ShaderStage::TesselationEval:
+ return regs.shader_config[static_cast<size_t>(Regs::ShaderProgram::TesselationEval)]
+ .enable != 0;
+ case Regs::ShaderStage::Geometry:
+ return regs.shader_config[static_cast<size_t>(Regs::ShaderProgram::Geometry)].enable != 0;
+ case Regs::ShaderStage::Fragment:
+ return regs.shader_config[static_cast<size_t>(Regs::ShaderProgram::Fragment)].enable != 0;
}
+
+ UNREACHABLE();
}
+
} // namespace Engines
} // namespace Tegra
diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h
index 1eeef6857..2dc251205 100644
--- a/src/video_core/engines/maxwell_3d.h
+++ b/src/video_core/engines/maxwell_3d.h
@@ -4,35 +4,538 @@
#pragma once
+#include <array>
+#include <unordered_map>
+#include <vector>
+#include "common/assert.h"
#include "common/bit_field.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
+#include "common/math_util.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 Tegra {
namespace Engines {
+#define MAXWELL3D_REG_INDEX(field_name) \
+ (offsetof(Tegra::Engines::Maxwell3D::Regs, field_name) / sizeof(u32))
+
class Maxwell3D final {
public:
explicit Maxwell3D(MemoryManager& memory_manager);
~Maxwell3D() = default;
- /// Write the value to the register identified by method.
- void WriteReg(u32 method, u32 value);
-
/// Register structure of the Maxwell3D engine.
/// TODO(Subv): This structure will need to be made bigger as more registers are discovered.
struct Regs {
- static constexpr size_t NUM_REGS = 0xE36;
+ static constexpr size_t NUM_REGS = 0xE00;
+
+ static constexpr size_t NumRenderTargets = 8;
+ static constexpr size_t NumViewports = 16;
+ static constexpr size_t NumCBData = 16;
+ static constexpr size_t NumVertexArrays = 32;
+ static constexpr size_t NumVertexAttributes = 32;
+ static constexpr size_t MaxShaderProgram = 6;
+ static constexpr size_t MaxShaderStage = 5;
+ // Maximum number of const buffers per shader stage.
+ static constexpr size_t MaxConstBuffers = 16;
enum class QueryMode : u32 {
Write = 0,
Sync = 1,
+ // TODO(Subv): It is currently unknown what the difference between method 2 and method 0
+ // is.
+ Write2 = 2,
+ };
+
+ enum class QueryUnit : u32 {
+ VFetch = 1,
+ VP = 2,
+ Rast = 4,
+ StrmOut = 5,
+ GP = 6,
+ ZCull = 7,
+ Prop = 10,
+ Crop = 15,
+ };
+
+ enum class QuerySelect : u32 {
+ Zero = 0,
+ };
+
+ enum class QuerySyncCondition : u32 {
+ NotEqual = 0,
+ GreaterThan = 1,
+ };
+
+ enum class ShaderProgram : u32 {
+ VertexA = 0,
+ VertexB = 1,
+ TesselationControl = 2,
+ TesselationEval = 3,
+ Geometry = 4,
+ Fragment = 5,
+ };
+
+ enum class ShaderStage : u32 {
+ Vertex = 0,
+ TesselationControl = 1,
+ TesselationEval = 2,
+ Geometry = 3,
+ Fragment = 4,
+ };
+
+ struct VertexAttribute {
+ enum class Size : u32 {
+ Size_32_32_32_32 = 0x01,
+ Size_32_32_32 = 0x02,
+ Size_16_16_16_16 = 0x03,
+ Size_32_32 = 0x04,
+ Size_16_16_16 = 0x05,
+ Size_8_8_8_8 = 0x0a,
+ Size_16_16 = 0x0f,
+ Size_32 = 0x12,
+ Size_8_8_8 = 0x13,
+ Size_8_8 = 0x18,
+ Size_16 = 0x1b,
+ Size_8 = 0x1d,
+ Size_10_10_10_2 = 0x30,
+ Size_11_11_10 = 0x31,
+ };
+
+ enum class Type : u32 {
+ SignedNorm = 1,
+ UnsignedNorm = 2,
+ SignedInt = 3,
+ UnsignedInt = 4,
+ UnsignedScaled = 5,
+ SignedScaled = 6,
+ Float = 7,
+ };
+
+ union {
+ BitField<0, 5, u32> buffer;
+ BitField<6, 1, u32> constant;
+ BitField<7, 14, u32> offset;
+ BitField<21, 6, Size> size;
+ BitField<27, 3, Type> type;
+ BitField<31, 1, u32> bgra;
+ };
+
+ u32 ComponentCount() const {
+ switch (size) {
+ case Size::Size_32_32_32_32:
+ return 4;
+ case Size::Size_32_32_32:
+ return 3;
+ case Size::Size_16_16_16_16:
+ return 4;
+ case Size::Size_32_32:
+ return 2;
+ case Size::Size_16_16_16:
+ return 3;
+ case Size::Size_8_8_8_8:
+ return 4;
+ case Size::Size_16_16:
+ return 2;
+ case Size::Size_32:
+ return 1;
+ case Size::Size_8_8_8:
+ return 3;
+ case Size::Size_8_8:
+ return 2;
+ case Size::Size_16:
+ return 1;
+ case Size::Size_8:
+ return 1;
+ case Size::Size_10_10_10_2:
+ return 4;
+ case Size::Size_11_11_10:
+ return 3;
+ default:
+ UNREACHABLE();
+ }
+ }
+
+ u32 SizeInBytes() const {
+ switch (size) {
+ case Size::Size_32_32_32_32:
+ return 16;
+ case Size::Size_32_32_32:
+ return 12;
+ case Size::Size_16_16_16_16:
+ return 8;
+ case Size::Size_32_32:
+ return 8;
+ case Size::Size_16_16_16:
+ return 6;
+ case Size::Size_8_8_8_8:
+ return 4;
+ case Size::Size_16_16:
+ return 4;
+ case Size::Size_32:
+ return 4;
+ case Size::Size_8_8_8:
+ return 3;
+ case Size::Size_8_8:
+ return 2;
+ case Size::Size_16:
+ return 2;
+ case Size::Size_8:
+ return 1;
+ case Size::Size_10_10_10_2:
+ return 4;
+ case Size::Size_11_11_10:
+ return 4;
+ default:
+ UNREACHABLE();
+ }
+ }
+
+ std::string SizeString() const {
+ switch (size) {
+ case Size::Size_32_32_32_32:
+ return "32_32_32_32";
+ case Size::Size_32_32_32:
+ return "32_32_32";
+ case Size::Size_16_16_16_16:
+ return "16_16_16_16";
+ case Size::Size_32_32:
+ return "32_32";
+ case Size::Size_16_16_16:
+ return "16_16_16";
+ case Size::Size_8_8_8_8:
+ return "8_8_8_8";
+ case Size::Size_16_16:
+ return "16_16";
+ case Size::Size_32:
+ return "32";
+ case Size::Size_8_8_8:
+ return "8_8_8";
+ case Size::Size_8_8:
+ return "8_8";
+ case Size::Size_16:
+ return "16";
+ case Size::Size_8:
+ return "8";
+ case Size::Size_10_10_10_2:
+ return "10_10_10_2";
+ case Size::Size_11_11_10:
+ return "11_11_10";
+ }
+ UNREACHABLE();
+ return {};
+ }
+
+ std::string TypeString() const {
+ switch (type) {
+ case Type::SignedNorm:
+ return "SNORM";
+ case Type::UnsignedNorm:
+ return "UNORM";
+ case Type::SignedInt:
+ return "SINT";
+ case Type::UnsignedInt:
+ return "UINT";
+ case Type::UnsignedScaled:
+ return "USCALED";
+ case Type::SignedScaled:
+ return "SSCALED";
+ case Type::Float:
+ return "FLOAT";
+ }
+ UNREACHABLE();
+ return {};
+ }
+
+ bool IsNormalized() const {
+ return (type == Type::SignedNorm) || (type == Type::UnsignedNorm);
+ }
+ };
+
+ enum class PrimitiveTopology : u32 {
+ Points = 0x0,
+ Lines = 0x1,
+ LineLoop = 0x2,
+ LineStrip = 0x3,
+ Triangles = 0x4,
+ TriangleStrip = 0x5,
+ TriangleFan = 0x6,
+ Quads = 0x7,
+ QuadStrip = 0x8,
+ Polygon = 0x9,
+ LinesAdjacency = 0xa,
+ LineStripAdjacency = 0xb,
+ TrianglesAdjacency = 0xc,
+ TriangleStripAdjacency = 0xd,
+ Patches = 0xe,
+ };
+
+ enum class IndexFormat : u32 {
+ UnsignedByte = 0x0,
+ UnsignedShort = 0x1,
+ UnsignedInt = 0x2,
+ };
+
+ struct Blend {
+ enum class Equation : u32 {
+ Add = 1,
+ Subtract = 2,
+ ReverseSubtract = 3,
+ Min = 4,
+ Max = 5,
+ };
+
+ enum class Factor : u32 {
+ Zero = 0x1,
+ One = 0x2,
+ SourceColor = 0x3,
+ OneMinusSourceColor = 0x4,
+ SourceAlpha = 0x5,
+ OneMinusSourceAlpha = 0x6,
+ DestAlpha = 0x7,
+ OneMinusDestAlpha = 0x8,
+ DestColor = 0x9,
+ OneMinusDestColor = 0xa,
+ SourceAlphaSaturate = 0xb,
+ Source1Color = 0x10,
+ OneMinusSource1Color = 0x11,
+ Source1Alpha = 0x12,
+ OneMinusSource1Alpha = 0x13,
+ ConstantColor = 0x61,
+ OneMinusConstantColor = 0x62,
+ ConstantAlpha = 0x63,
+ OneMinusConstantAlpha = 0x64,
+ };
+
+ u32 separate_alpha;
+ Equation equation_rgb;
+ Factor factor_source_rgb;
+ Factor factor_dest_rgb;
+ Equation equation_a;
+ Factor factor_source_a;
+ Factor factor_dest_a;
+ INSERT_PADDING_WORDS(1);
};
union {
struct {
- INSERT_PADDING_WORDS(0x6C0);
+ INSERT_PADDING_WORDS(0x45);
+
+ struct {
+ INSERT_PADDING_WORDS(1);
+ u32 data;
+ u32 entry;
+ } macros;
+
+ INSERT_PADDING_WORDS(0x1B8);
+
+ struct {
+ u32 address_high;
+ u32 address_low;
+ u32 width;
+ u32 height;
+ Tegra::RenderTargetFormat format;
+ u32 block_dimensions;
+ u32 array_mode;
+ u32 layer_stride;
+ u32 base_layer;
+ INSERT_PADDING_WORDS(7);
+
+ GPUVAddr Address() const {
+ return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) |
+ address_low);
+ }
+ } rt[NumRenderTargets];
+
+ struct {
+ f32 scale_x;
+ f32 scale_y;
+ f32 scale_z;
+ f32 translate_x;
+ f32 translate_y;
+ f32 translate_z;
+ INSERT_PADDING_WORDS(2);
+
+ MathUtil::Rectangle<s32> GetRect() const {
+ return {
+ GetX(), // left
+ GetY() + GetHeight(), // top
+ GetX() + GetWidth(), // right
+ GetY() // bottom
+ };
+ };
+
+ s32 GetX() const {
+ return static_cast<s32>(std::max(0.0f, translate_x - std::fabs(scale_x)));
+ }
+
+ s32 GetY() const {
+ return static_cast<s32>(std::max(0.0f, translate_y - std::fabs(scale_y)));
+ }
+
+ s32 GetWidth() const {
+ return static_cast<s32>(translate_x + std::fabs(scale_x)) - GetX();
+ }
+
+ s32 GetHeight() const {
+ return static_cast<s32>(translate_y + std::fabs(scale_y)) - GetY();
+ }
+ } viewport_transform[NumViewports];
+
+ struct {
+ union {
+ BitField<0, 16, u32> x;
+ BitField<16, 16, u32> width;
+ };
+ union {
+ BitField<0, 16, u32> y;
+ BitField<16, 16, u32> height;
+ };
+ float depth_range_near;
+ float depth_range_far;
+ } viewport[NumViewports];
+
+ INSERT_PADDING_WORDS(0x1D);
+
+ struct {
+ u32 first;
+ u32 count;
+ } vertex_buffer;
+
+ INSERT_PADDING_WORDS(0x99);
+
+ struct {
+ u32 address_high;
+ u32 address_low;
+ u32 format;
+ u32 block_dimensions;
+ u32 layer_stride;
+
+ GPUVAddr Address() const {
+ return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) |
+ address_low);
+ }
+ } zeta;
+
+ INSERT_PADDING_WORDS(0x5B);
+
+ VertexAttribute vertex_attrib_format[NumVertexAttributes];
+
+ INSERT_PADDING_WORDS(0xF);
+
+ struct {
+ union {
+ BitField<0, 4, u32> count;
+ };
+ } rt_control;
+
+ INSERT_PADDING_WORDS(0x31);
+
+ u32 independent_blend_enable;
+
+ INSERT_PADDING_WORDS(0x15);
+
+ struct {
+ u32 separate_alpha;
+ Blend::Equation equation_rgb;
+ Blend::Factor factor_source_rgb;
+ Blend::Factor factor_dest_rgb;
+ Blend::Equation equation_a;
+ Blend::Factor factor_source_a;
+ INSERT_PADDING_WORDS(1);
+ Blend::Factor factor_dest_a;
+
+ u32 enable_common;
+ u32 enable[NumRenderTargets];
+ } blend;
+
+ INSERT_PADDING_WORDS(0x77);
+
+ struct {
+ u32 tsc_address_high;
+ u32 tsc_address_low;
+ u32 tsc_limit;
+
+ GPUVAddr TSCAddress() const {
+ return static_cast<GPUVAddr>(
+ (static_cast<GPUVAddr>(tsc_address_high) << 32) | tsc_address_low);
+ }
+ } tsc;
+
+ INSERT_PADDING_WORDS(0x3);
+
+ struct {
+ u32 tic_address_high;
+ u32 tic_address_low;
+ u32 tic_limit;
+
+ GPUVAddr TICAddress() const {
+ return static_cast<GPUVAddr>(
+ (static_cast<GPUVAddr>(tic_address_high) << 32) | tic_address_low);
+ }
+ } tic;
+
+ INSERT_PADDING_WORDS(0x22);
+
+ struct {
+ u32 code_address_high;
+ u32 code_address_low;
+
+ GPUVAddr CodeAddress() const {
+ return static_cast<GPUVAddr>(
+ (static_cast<GPUVAddr>(code_address_high) << 32) | code_address_low);
+ }
+ } code_address;
+ INSERT_PADDING_WORDS(1);
+
+ struct {
+ u32 vertex_end_gl;
+ union {
+ u32 vertex_begin_gl;
+ BitField<0, 16, PrimitiveTopology> topology;
+ };
+ } draw;
+
+ INSERT_PADDING_WORDS(0x6B);
+
+ struct {
+ u32 start_addr_high;
+ u32 start_addr_low;
+ u32 end_addr_high;
+ u32 end_addr_low;
+ IndexFormat format;
+ u32 first;
+ u32 count;
+
+ unsigned FormatSizeInBytes() const {
+ switch (format) {
+ case IndexFormat::UnsignedByte:
+ return 1;
+ case IndexFormat::UnsignedShort:
+ return 2;
+ case IndexFormat::UnsignedInt:
+ return 4;
+ }
+ UNREACHABLE();
+ }
+
+ GPUVAddr StartAddress() const {
+ return static_cast<GPUVAddr>(
+ (static_cast<GPUVAddr>(start_addr_high) << 32) | start_addr_low);
+ }
+
+ GPUVAddr EndAddress() const {
+ return static_cast<GPUVAddr>((static_cast<GPUVAddr>(end_addr_high) << 32) |
+ end_addr_low);
+ }
+ } index_array;
+
+ INSERT_PADDING_WORDS(0xC7);
+
struct {
u32 query_address_high;
u32 query_address_low;
@@ -41,7 +544,10 @@ public:
u32 raw;
BitField<0, 2, QueryMode> mode;
BitField<4, 1, u32> fence;
- BitField<12, 4, u32> unit;
+ BitField<12, 4, QueryUnit> unit;
+ BitField<16, 1, QuerySyncCondition> sync_cond;
+ BitField<23, 5, QuerySelect> select;
+ BitField<28, 1, u32> short_query;
} query_get;
GPUVAddr QueryAddress() const {
@@ -49,7 +555,100 @@ public:
(static_cast<GPUVAddr>(query_address_high) << 32) | query_address_low);
}
} query;
- INSERT_PADDING_WORDS(0x772);
+
+ INSERT_PADDING_WORDS(0x3C);
+
+ struct {
+ union {
+ BitField<0, 12, u32> stride;
+ BitField<12, 1, u32> enable;
+ };
+ u32 start_high;
+ u32 start_low;
+ u32 divisor;
+
+ GPUVAddr StartAddress() const {
+ return static_cast<GPUVAddr>((static_cast<GPUVAddr>(start_high) << 32) |
+ start_low);
+ }
+
+ bool IsEnabled() const {
+ return enable != 0 && StartAddress() != 0;
+ }
+
+ } vertex_array[NumVertexArrays];
+
+ Blend independent_blend[NumRenderTargets];
+
+ struct {
+ u32 limit_high;
+ u32 limit_low;
+
+ GPUVAddr LimitAddress() const {
+ return static_cast<GPUVAddr>((static_cast<GPUVAddr>(limit_high) << 32) |
+ limit_low);
+ }
+ } vertex_array_limit[NumVertexArrays];
+
+ struct {
+ union {
+ BitField<0, 1, u32> enable;
+ BitField<4, 4, ShaderProgram> program;
+ };
+ u32 offset;
+ INSERT_PADDING_WORDS(14);
+ } shader_config[MaxShaderProgram];
+
+ INSERT_PADDING_WORDS(0x80);
+
+ struct {
+ u32 cb_size;
+ u32 cb_address_high;
+ u32 cb_address_low;
+ u32 cb_pos;
+ u32 cb_data[NumCBData];
+
+ GPUVAddr BufferAddress() const {
+ return static_cast<GPUVAddr>(
+ (static_cast<GPUVAddr>(cb_address_high) << 32) | cb_address_low);
+ }
+ } const_buffer;
+
+ INSERT_PADDING_WORDS(0x10);
+
+ struct {
+ union {
+ u32 raw_config;
+ BitField<0, 1, u32> valid;
+ BitField<4, 5, u32> index;
+ };
+ INSERT_PADDING_WORDS(7);
+ } cb_bind[MaxShaderStage];
+
+ INSERT_PADDING_WORDS(0x56);
+
+ u32 tex_cb_index;
+
+ INSERT_PADDING_WORDS(0x395);
+
+ struct {
+ /// Compressed address of a buffer that holds information about bound SSBOs.
+ /// This address is usually bound to c0 in the shaders.
+ u32 buffer_address;
+
+ GPUVAddr BufferAddress() const {
+ return static_cast<GPUVAddr>(buffer_address) << 8;
+ }
+ } ssbo_info;
+
+ INSERT_PADDING_WORDS(0x11);
+
+ struct {
+ u32 address[MaxShaderStage];
+ u32 size[MaxShaderStage];
+ } tex_info_buffers;
+
+ INSERT_PADDING_WORDS(0xCC);
};
std::array<u32, NUM_REGS> reg_array;
};
@@ -57,18 +656,109 @@ public:
static_assert(sizeof(Regs) == Regs::NUM_REGS * sizeof(u32), "Maxwell3D Regs has wrong size");
+ struct State {
+ struct ConstBufferInfo {
+ GPUVAddr address;
+ u32 index;
+ u32 size;
+ bool enabled;
+ };
+
+ struct ShaderStageInfo {
+ std::array<ConstBufferInfo, Regs::MaxConstBuffers> const_buffers;
+ };
+
+ std::array<ShaderStageInfo, Regs::MaxShaderStage> shader_stages;
+ };
+
+ State state{};
+ MemoryManager& memory_manager;
+
+ /// Reads a register value located at the input method address
+ u32 GetRegisterValue(u32 method) const;
+
+ /// Write the value to the register identified by method.
+ void WriteReg(u32 method, u32 value, u32 remaining_params);
+
+ /// 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, size_t offset) const;
+
+ /// Returns whether the specified shader stage is enabled or not.
+ bool IsShaderStageEnabled(Regs::ShaderStage stage) const;
+
private:
+ std::unordered_map<u32, std::vector<u32>> uploaded_macros;
+
+ /// Macro method that is currently being executed / being fed parameters.
+ u32 executing_macro = 0;
+ /// Parameters that have been submitted to the macro call so far.
+ std::vector<u32> macro_params;
+
+ /// Interpreter for the macro codes uploaded to the GPU.
+ MacroInterpreter macro_interpreter;
+
+ /// Retrieves information about a specific TIC entry from the TIC buffer.
+ Texture::TICEntry GetTICEntry(u32 tic_index) const;
+
+ /// Retrieves information about a specific TSC entry from the TSC buffer.
+ Texture::TSCEntry GetTSCEntry(u32 tsc_index) const;
+
+ /**
+ * Call a macro on this engine.
+ * @param method Method to call
+ * @param parameters Arguments to the method call
+ */
+ void CallMacroMethod(u32 method, std::vector<u32> parameters);
+
+ /// Handles writes to the macro uploading registers.
+ void ProcessMacroUpload(u32 data);
+
/// Handles a write to the QUERY_GET register.
void ProcessQueryGet();
- MemoryManager& memory_manager;
+ /// Handles a write to the CB_DATA[i] register.
+ void ProcessCBData(u32 value);
+
+ /// Handles a write to the CB_BIND register.
+ void ProcessCBBind(Regs::ShaderStage stage);
+
+ /// Handles a write to the VERTEX_END_GL register, triggering a draw.
+ void DrawArrays();
};
#define ASSERT_REG_POSITION(field_name, position) \
static_assert(offsetof(Maxwell3D::Regs, field_name) == position * 4, \
"Field " #field_name " has invalid position")
+ASSERT_REG_POSITION(macros, 0x45);
+ASSERT_REG_POSITION(rt, 0x200);
+ASSERT_REG_POSITION(viewport_transform[0], 0x280);
+ASSERT_REG_POSITION(viewport, 0x300);
+ASSERT_REG_POSITION(vertex_buffer, 0x35D);
+ASSERT_REG_POSITION(zeta, 0x3F8);
+ASSERT_REG_POSITION(vertex_attrib_format[0], 0x458);
+ASSERT_REG_POSITION(rt_control, 0x487);
+ASSERT_REG_POSITION(independent_blend_enable, 0x4B9);
+ASSERT_REG_POSITION(blend, 0x4CF);
+ASSERT_REG_POSITION(tsc, 0x557);
+ASSERT_REG_POSITION(tic, 0x55D);
+ASSERT_REG_POSITION(code_address, 0x582);
+ASSERT_REG_POSITION(draw, 0x585);
+ASSERT_REG_POSITION(index_array, 0x5F2);
ASSERT_REG_POSITION(query, 0x6C0);
+ASSERT_REG_POSITION(vertex_array[0], 0x700);
+ASSERT_REG_POSITION(independent_blend, 0x780);
+ASSERT_REG_POSITION(vertex_array_limit[0], 0x7C0);
+ASSERT_REG_POSITION(shader_config[0], 0x800);
+ASSERT_REG_POSITION(const_buffer, 0x8E0);
+ASSERT_REG_POSITION(cb_bind[0], 0x904);
+ASSERT_REG_POSITION(tex_cb_index, 0x982);
+ASSERT_REG_POSITION(ssbo_info, 0xD18);
+ASSERT_REG_POSITION(tex_info_buffers.address[0], 0xD2A);
+ASSERT_REG_POSITION(tex_info_buffers.size[0], 0xD2F);
#undef ASSERT_REG_POSITION
diff --git a/src/video_core/engines/maxwell_dma.cpp b/src/video_core/engines/maxwell_dma.cpp
new file mode 100644
index 000000000..442138988
--- /dev/null
+++ b/src/video_core/engines/maxwell_dma.cpp
@@ -0,0 +1,69 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/memory.h"
+#include "video_core/engines/maxwell_dma.h"
+#include "video_core/textures/decoders.h"
+
+namespace Tegra {
+namespace Engines {
+
+MaxwellDMA::MaxwellDMA(MemoryManager& memory_manager) : memory_manager(memory_manager) {}
+
+void MaxwellDMA::WriteReg(u32 method, u32 value) {
+ ASSERT_MSG(method < Regs::NUM_REGS,
+ "Invalid MaxwellDMA register, increase the size of the Regs structure");
+
+ regs.reg_array[method] = value;
+
+#define MAXWELLDMA_REG_INDEX(field_name) \
+ (offsetof(Tegra::Engines::MaxwellDMA::Regs, field_name) / sizeof(u32))
+
+ switch (method) {
+ case MAXWELLDMA_REG_INDEX(exec): {
+ HandleCopy();
+ break;
+ }
+ }
+
+#undef MAXWELLDMA_REG_INDEX
+}
+
+void MaxwellDMA::HandleCopy() {
+ NGLOG_WARNING(HW_GPU, "Requested a DMA copy");
+
+ const GPUVAddr source = regs.src_address.Address();
+ const GPUVAddr dest = regs.dst_address.Address();
+
+ const VAddr source_cpu = *memory_manager.GpuToCpuAddress(source);
+ const VAddr dest_cpu = *memory_manager.GpuToCpuAddress(dest);
+
+ // TODO(Subv): Perform more research and implement all features of this engine.
+ ASSERT(regs.exec.enable_swizzle == 0);
+ ASSERT(regs.exec.enable_2d == 1);
+ ASSERT(regs.exec.query_mode == Regs::QueryMode::None);
+ ASSERT(regs.exec.query_intr == Regs::QueryIntr::None);
+ ASSERT(regs.exec.copy_mode == Regs::CopyMode::Unk2);
+ ASSERT(regs.src_params.pos_x == 0);
+ ASSERT(regs.src_params.pos_y == 0);
+ ASSERT(regs.dst_params.pos_x == 0);
+ ASSERT(regs.dst_params.pos_y == 0);
+ ASSERT(regs.exec.is_dst_linear != regs.exec.is_src_linear);
+
+ u8* src_buffer = Memory::GetPointer(source_cpu);
+ u8* dst_buffer = Memory::GetPointer(dest_cpu);
+
+ if (regs.exec.is_dst_linear && !regs.exec.is_src_linear) {
+ // If the input is tiled and the output is linear, deswizzle the input and copy it over.
+ Texture::CopySwizzledData(regs.src_params.size_x, regs.src_params.size_y, 1, 1, src_buffer,
+ dst_buffer, true, regs.src_params.BlockHeight());
+ } else {
+ // If the input is linear and the output is tiled, swizzle the input and copy it over.
+ Texture::CopySwizzledData(regs.dst_params.size_x, regs.dst_params.size_y, 1, 1, dst_buffer,
+ src_buffer, false, regs.dst_params.BlockHeight());
+ }
+}
+
+} // namespace Engines
+} // namespace Tegra
diff --git a/src/video_core/engines/maxwell_dma.h b/src/video_core/engines/maxwell_dma.h
new file mode 100644
index 000000000..905749bde
--- /dev/null
+++ b/src/video_core/engines/maxwell_dma.h
@@ -0,0 +1,155 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#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/gpu.h"
+#include "video_core/memory_manager.h"
+
+namespace Tegra {
+namespace Engines {
+
+class MaxwellDMA final {
+public:
+ explicit MaxwellDMA(MemoryManager& memory_manager);
+ ~MaxwellDMA() = default;
+
+ /// Write the value to the register identified by method.
+ void WriteReg(u32 method, u32 value);
+
+ struct Regs {
+ static constexpr size_t NUM_REGS = 0x1D6;
+
+ struct Parameters {
+ union {
+ BitField<0, 4, u32> block_depth;
+ BitField<4, 4, u32> block_height;
+ BitField<8, 4, u32> block_width;
+ };
+ u32 size_x;
+ u32 size_y;
+ u32 size_z;
+ u32 pos_z;
+ union {
+ BitField<0, 16, u32> pos_x;
+ BitField<16, 16, u32> pos_y;
+ };
+
+ u32 BlockHeight() const {
+ return 1 << block_height;
+ }
+ };
+
+ static_assert(sizeof(Parameters) == 24, "Parameters has wrong size");
+
+ enum class CopyMode : u32 {
+ None = 0,
+ Unk1 = 1,
+ Unk2 = 2,
+ };
+
+ enum class QueryMode : u32 {
+ None = 0,
+ Short = 1,
+ Long = 2,
+ };
+
+ enum class QueryIntr : u32 {
+ None = 0,
+ Block = 1,
+ NonBlock = 2,
+ };
+
+ union {
+ struct {
+ INSERT_PADDING_WORDS(0xC0);
+
+ struct {
+ union {
+ BitField<0, 2, CopyMode> copy_mode;
+ BitField<2, 1, u32> flush;
+
+ BitField<3, 2, QueryMode> query_mode;
+ BitField<5, 2, QueryIntr> query_intr;
+
+ BitField<7, 1, u32> is_src_linear;
+ BitField<8, 1, u32> is_dst_linear;
+
+ BitField<9, 1, u32> enable_2d;
+ BitField<10, 1, u32> enable_swizzle;
+ };
+ } exec;
+
+ INSERT_PADDING_WORDS(0x3F);
+
+ struct {
+ u32 address_high;
+ u32 address_low;
+
+ GPUVAddr Address() const {
+ return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) |
+ address_low);
+ }
+ } src_address;
+
+ struct {
+ u32 address_high;
+ u32 address_low;
+
+ GPUVAddr Address() const {
+ return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) |
+ address_low);
+ }
+ } dst_address;
+
+ u32 src_pitch;
+ u32 dst_pitch;
+ u32 x_count;
+ u32 y_count;
+
+ INSERT_PADDING_WORDS(0xBB);
+
+ Parameters dst_params;
+
+ INSERT_PADDING_WORDS(1);
+
+ Parameters src_params;
+
+ INSERT_PADDING_WORDS(0x13);
+ };
+ std::array<u32, NUM_REGS> reg_array;
+ };
+ } regs{};
+
+ MemoryManager& memory_manager;
+
+private:
+ /// Performs the copy from the source buffer to the destination buffer as configured in the
+ /// registers.
+ void HandleCopy();
+};
+
+#define ASSERT_REG_POSITION(field_name, position) \
+ static_assert(offsetof(MaxwellDMA::Regs, field_name) == position * 4, \
+ "Field " #field_name " has invalid position")
+
+ASSERT_REG_POSITION(exec, 0xC0);
+ASSERT_REG_POSITION(src_address, 0x100);
+ASSERT_REG_POSITION(dst_address, 0x102);
+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(dst_params, 0x1C3);
+ASSERT_REG_POSITION(src_params, 0x1CA);
+
+#undef ASSERT_REG_POSITION
+
+} // namespace Engines
+} // namespace Tegra
diff --git a/src/video_core/engines/shader_bytecode.h b/src/video_core/engines/shader_bytecode.h
new file mode 100644
index 000000000..cefd57f4c
--- /dev/null
+++ b/src/video_core/engines/shader_bytecode.h
@@ -0,0 +1,735 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <bitset>
+#include <cstring>
+#include <map>
+#include <string>
+#include <vector>
+
+#include <boost/optional.hpp>
+
+#include "common/bit_field.h"
+#include "common/common_types.h"
+
+namespace Tegra {
+namespace Shader {
+
+struct Register {
+ /// Number of registers
+ static constexpr size_t NumRegisters = 256;
+
+ /// Register 255 is special cased to always be 0
+ static constexpr size_t ZeroIndex = 255;
+
+ enum class Size : u64 {
+ Byte = 0,
+ Short = 1,
+ Word = 2,
+ Long = 3,
+ };
+
+ constexpr Register() = default;
+
+ constexpr Register(u64 value) : value(value) {}
+
+ constexpr operator u64() const {
+ return value;
+ }
+
+ template <typename T>
+ constexpr u64 operator-(const T& oth) const {
+ return value - oth;
+ }
+
+ template <typename T>
+ constexpr u64 operator&(const T& oth) const {
+ return value & oth;
+ }
+
+ constexpr u64 operator&(const Register& oth) const {
+ return value & oth.value;
+ }
+
+ constexpr u64 operator~() const {
+ return ~value;
+ }
+
+ u64 GetSwizzledIndex(u64 elem) const {
+ elem = (value + elem) & 3;
+ return (value & ~3) + elem;
+ }
+
+private:
+ u64 value{};
+};
+
+union Attribute {
+ Attribute() = default;
+
+ constexpr explicit Attribute(u64 value) : value(value) {}
+
+ enum class Index : u64 {
+ Position = 7,
+ Attribute_0 = 8,
+ // This attribute contains a tuple of (~, ~, InstanceId, VertexId) when inside a vertex
+ // shader, and a tuple of (TessCoord.x, TessCoord.y, TessCoord.z, ~) when inside a Tess Eval
+ // shader.
+ TessCoordInstanceIDVertexID = 47,
+ };
+
+ union {
+ BitField<22, 2, u64> element;
+ BitField<24, 6, Index> index;
+ BitField<47, 3, u64> size;
+ } fmt20;
+
+ union {
+ BitField<30, 2, u64> element;
+ BitField<32, 6, Index> index;
+ } fmt28;
+
+ BitField<39, 8, u64> reg;
+ u64 value{};
+};
+
+union Sampler {
+ Sampler() = default;
+
+ constexpr explicit Sampler(u64 value) : value(value) {}
+
+ enum class Index : u64 {
+ Sampler_0 = 8,
+ };
+
+ BitField<36, 13, Index> index;
+ u64 value{};
+};
+
+} // namespace Shader
+} // namespace Tegra
+
+namespace std {
+
+// TODO(bunnei): The below is forbidden by the C++ standard, but works fine. See #330.
+template <>
+struct make_unsigned<Tegra::Shader::Attribute> {
+ using type = Tegra::Shader::Attribute;
+};
+
+template <>
+struct make_unsigned<Tegra::Shader::Register> {
+ using type = Tegra::Shader::Register;
+};
+
+} // namespace std
+
+namespace Tegra {
+namespace Shader {
+
+enum class Pred : u64 {
+ UnusedIndex = 0x7,
+ NeverExecute = 0xF,
+};
+
+enum class PredCondition : u64 {
+ LessThan = 1,
+ Equal = 2,
+ LessEqual = 3,
+ GreaterThan = 4,
+ NotEqual = 5,
+ GreaterEqual = 6,
+ // TODO(Subv): Other condition types
+};
+
+enum class PredOperation : u64 {
+ And = 0,
+ Or = 1,
+ Xor = 2,
+};
+
+enum class LogicOperation : u64 {
+ And = 0,
+ Or = 1,
+ Xor = 2,
+ PassB = 3,
+};
+
+enum class SubOp : u64 {
+ Cos = 0x0,
+ Sin = 0x1,
+ Ex2 = 0x2,
+ Lg2 = 0x3,
+ Rcp = 0x4,
+ Rsq = 0x5,
+ Min = 0x8,
+};
+
+enum class F2iRoundingOp : u64 {
+ None = 0,
+ Floor = 1,
+ Ceil = 2,
+ Trunc = 3,
+};
+
+enum class F2fRoundingOp : u64 {
+ None = 0,
+ Pass = 3,
+ Round = 8,
+ Floor = 9,
+ Ceil = 10,
+ Trunc = 11,
+};
+
+enum class UniformType : u64 {
+ UnsignedByte = 0,
+ SignedByte = 1,
+ UnsignedShort = 2,
+ SignedShort = 3,
+ Single = 4,
+ Double = 5,
+};
+
+union Instruction {
+ Instruction& operator=(const Instruction& instr) {
+ value = instr.value;
+ return *this;
+ }
+
+ constexpr Instruction(u64 value) : value{value} {}
+
+ BitField<0, 8, Register> gpr0;
+ BitField<8, 8, Register> gpr8;
+ union {
+ BitField<16, 4, Pred> full_pred;
+ BitField<16, 3, u64> pred_index;
+ } pred;
+ BitField<19, 1, u64> negate_pred;
+ BitField<20, 8, Register> gpr20;
+ BitField<20, 7, SubOp> sub_op;
+ BitField<28, 8, Register> gpr28;
+ BitField<39, 8, Register> gpr39;
+ BitField<48, 16, u64> opcode;
+
+ union {
+ BitField<20, 19, u64> imm20_19;
+ BitField<20, 32, s64> imm20_32;
+ BitField<45, 1, u64> negate_b;
+ BitField<46, 1, u64> abs_a;
+ BitField<48, 1, u64> negate_a;
+ BitField<49, 1, u64> abs_b;
+ BitField<50, 1, u64> saturate_d;
+ BitField<56, 1, u64> negate_imm;
+
+ union {
+ BitField<39, 3, u64> pred;
+ BitField<42, 1, u64> negate_pred;
+ } fmnmx;
+
+ union {
+ BitField<39, 1, u64> invert_a;
+ BitField<40, 1, u64> invert_b;
+ BitField<41, 2, LogicOperation> operation;
+ BitField<44, 2, u64> unk44;
+ BitField<48, 3, Pred> pred48;
+ } lop;
+
+ union {
+ BitField<53, 2, LogicOperation> operation;
+ BitField<55, 1, u64> invert_a;
+ BitField<56, 1, u64> invert_b;
+ } lop32i;
+
+ float GetImm20_19() const {
+ float result{};
+ u32 imm{static_cast<u32>(imm20_19)};
+ imm <<= 12;
+ imm |= negate_imm ? 0x80000000 : 0;
+ std::memcpy(&result, &imm, sizeof(imm));
+ return result;
+ }
+
+ float GetImm20_32() const {
+ float result{};
+ s32 imm{static_cast<s32>(imm20_32)};
+ std::memcpy(&result, &imm, sizeof(imm));
+ return result;
+ }
+
+ s32 GetSignedImm20_20() const {
+ u32 immediate = static_cast<u32>(imm20_19 | (negate_imm << 19));
+ // Sign extend the 20-bit value.
+ u32 mask = 1U << (20 - 1);
+ return static_cast<s32>((immediate ^ mask) - mask);
+ }
+ } alu;
+
+ union {
+ BitField<48, 1, u64> is_signed;
+ } shift;
+
+ union {
+ BitField<39, 5, u64> shift_amount;
+ BitField<48, 1, u64> negate_b;
+ BitField<49, 1, u64> negate_a;
+ } alu_integer;
+
+ union {
+ BitField<54, 1, u64> saturate;
+ BitField<56, 1, u64> negate_a;
+ } iadd32i;
+
+ union {
+ BitField<20, 8, u64> shift_position;
+ BitField<28, 8, u64> shift_length;
+ BitField<48, 1, u64> negate_b;
+ BitField<49, 1, u64> negate_a;
+
+ u64 GetLeftShiftValue() const {
+ return 32 - (shift_position + shift_length);
+ }
+ } bfe;
+
+ union {
+ BitField<48, 1, u64> negate_b;
+ BitField<49, 1, u64> negate_c;
+ } ffma;
+
+ union {
+ BitField<48, 3, UniformType> type;
+ BitField<44, 2, u64> unknown;
+ } ld_c;
+
+ union {
+ BitField<0, 3, u64> pred0;
+ BitField<3, 3, u64> pred3;
+ BitField<7, 1, u64> abs_a;
+ BitField<39, 3, u64> pred39;
+ BitField<42, 1, u64> neg_pred;
+ BitField<43, 1, u64> neg_a;
+ BitField<44, 1, u64> abs_b;
+ BitField<45, 2, PredOperation> op;
+ BitField<47, 1, u64> ftz;
+ BitField<48, 4, PredCondition> cond;
+ BitField<56, 1, u64> neg_b;
+ } fsetp;
+
+ union {
+ BitField<0, 3, u64> pred0;
+ BitField<3, 3, u64> pred3;
+ BitField<39, 3, u64> pred39;
+ BitField<42, 1, u64> neg_pred;
+ BitField<45, 2, PredOperation> op;
+ BitField<48, 1, u64> is_signed;
+ BitField<49, 3, PredCondition> cond;
+ } isetp;
+
+ union {
+ BitField<39, 3, u64> pred39;
+ BitField<42, 1, u64> neg_pred;
+ BitField<43, 1, u64> neg_a;
+ BitField<44, 1, u64> abs_b;
+ BitField<45, 2, PredOperation> op;
+ BitField<48, 4, PredCondition> cond;
+ BitField<52, 1, u64> bf;
+ BitField<53, 1, u64> neg_b;
+ BitField<54, 1, u64> abs_a;
+ BitField<55, 1, u64> ftz;
+ BitField<56, 1, u64> neg_imm;
+ } fset;
+
+ union {
+ BitField<39, 3, u64> pred39;
+ BitField<42, 1, u64> neg_pred;
+ BitField<44, 1, u64> bf;
+ BitField<45, 2, PredOperation> op;
+ BitField<48, 1, u64> is_signed;
+ BitField<49, 3, PredCondition> cond;
+ } 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<45, 1, u64> negate_a;
+ BitField<49, 1, u64> abs_a;
+
+ union {
+ BitField<39, 2, F2iRoundingOp> rounding;
+ } f2i;
+
+ union {
+ BitField<39, 4, F2fRoundingOp> rounding;
+ } f2f;
+ } conversion;
+
+ union {
+ BitField<31, 4, u64> component_mask;
+
+ bool IsComponentEnabled(size_t component) const {
+ return ((1 << component) & component_mask) != 0;
+ }
+ } tex;
+
+ union {
+ BitField<50, 3, u64> component_mask_selector;
+ BitField<28, 8, Register> gpr28;
+
+ bool HasTwoDestinations() const {
+ return gpr28.Value() != Register::ZeroIndex;
+ }
+
+ bool IsComponentEnabled(size_t component) const {
+ static constexpr std::array<size_t, 5> one_dest_mask{0x1, 0x2, 0x4, 0x8, 0x3};
+ static constexpr std::array<size_t, 5> two_dest_mask{0x7, 0xb, 0xd, 0xe, 0xf};
+ const auto& mask{HasTwoDestinations() ? two_dest_mask : one_dest_mask};
+
+ ASSERT(component_mask_selector < mask.size());
+
+ return ((1 << component) & mask[component_mask_selector]) != 0;
+ }
+ } texs;
+
+ union {
+ BitField<20, 24, u64> target;
+ BitField<5, 1, u64> constant_buffer;
+
+ s32 GetBranchTarget() const {
+ // Sign extend the branch target offset
+ u32 mask = 1U << (24 - 1);
+ u32 value = static_cast<u32>(target);
+ // The branch offset is relative to the next instruction and is stored in bytes, so
+ // divide it by the size of an instruction and add 1 to it.
+ return static_cast<s32>((value ^ mask) - mask) / sizeof(Instruction) + 1;
+ }
+ } bra;
+
+ union {
+ BitField<20, 14, u64> offset;
+ BitField<34, 5, u64> index;
+ } cbuf34;
+
+ union {
+ BitField<20, 16, s64> offset;
+ BitField<36, 5, u64> index;
+ } cbuf36;
+
+ BitField<61, 1, u64> is_b_imm;
+ BitField<60, 1, u64> is_b_gpr;
+ BitField<59, 1, u64> is_c_gpr;
+
+ Attribute attribute;
+ Sampler sampler;
+
+ u64 value;
+};
+static_assert(sizeof(Instruction) == 0x8, "Incorrect structure size");
+static_assert(std::is_standard_layout<Instruction>::value,
+ "Structure does not have standard layout");
+
+class OpCode {
+public:
+ enum class Id {
+ KIL,
+ SSY,
+ BFE_C,
+ BFE_R,
+ BFE_IMM,
+ BRA,
+ LD_A,
+ LD_C,
+ ST_A,
+ TEX,
+ TEXQ, // Texture Query
+ TEXS, // Texture Fetch with scalar/non-vec4 source/destinations
+ TLDS, // Texture Load with scalar/non-vec4 source/destinations
+ EXIT,
+ IPA,
+ FFMA_IMM, // Fused Multiply and Add
+ FFMA_CR,
+ FFMA_RC,
+ FFMA_RR,
+ FADD_C,
+ FADD_R,
+ FADD_IMM,
+ FMUL_C,
+ FMUL_R,
+ FMUL_IMM,
+ FMUL32_IMM,
+ IADD_C,
+ IADD_R,
+ IADD_IMM,
+ IADD32I,
+ ISCADD_C, // Scale and Add
+ ISCADD_R,
+ ISCADD_IMM,
+ MUFU, // Multi-Function Operator
+ RRO_C, // Range Reduction Operator
+ RRO_R,
+ RRO_IMM,
+ F2F_C,
+ F2F_R,
+ F2F_IMM,
+ F2I_C,
+ F2I_R,
+ F2I_IMM,
+ I2F_C,
+ I2F_R,
+ I2F_IMM,
+ I2I_C,
+ I2I_R,
+ I2I_IMM,
+ LOP_C,
+ LOP_R,
+ LOP_IMM,
+ LOP32I,
+ MOV_C,
+ MOV_R,
+ MOV_IMM,
+ MOV32_IMM,
+ SHL_C,
+ SHL_R,
+ SHL_IMM,
+ SHR_C,
+ SHR_R,
+ SHR_IMM,
+ FMNMX_C,
+ FMNMX_R,
+ FMNMX_IMM,
+ IMNMX_C,
+ IMNMX_R,
+ IMNMX_IMM,
+ FSETP_C, // Set Predicate
+ FSETP_R,
+ FSETP_IMM,
+ FSET_C,
+ FSET_R,
+ FSET_IMM,
+ ISETP_C,
+ ISETP_IMM,
+ ISETP_R,
+ ISET_R,
+ ISET_C,
+ ISET_IMM,
+ PSETP,
+ XMAD_IMM,
+ XMAD_CR,
+ XMAD_RC,
+ XMAD_RR,
+ };
+
+ enum class Type {
+ Trivial,
+ Arithmetic,
+ ArithmeticInteger,
+ ArithmeticIntegerImmediate,
+ Bfe,
+ Shift,
+ Ffma,
+ Flow,
+ Memory,
+ FloatSet,
+ FloatSetPredicate,
+ IntegerSet,
+ IntegerSetPredicate,
+ PredicateSetPredicate,
+ Conversion,
+ Unknown,
+ };
+
+ class Matcher {
+ public:
+ Matcher(const char* const name, u16 mask, u16 expected, OpCode::Id id, OpCode::Type type)
+ : name{name}, mask{mask}, expected{expected}, id{id}, type{type} {}
+
+ const char* GetName() const {
+ return name;
+ }
+
+ u16 GetMask() const {
+ return mask;
+ }
+
+ Id GetId() const {
+ return id;
+ }
+
+ Type GetType() const {
+ return type;
+ }
+
+ /**
+ * Tests to see if the given instruction is the instruction this matcher represents.
+ * @param instruction The instruction to test
+ * @returns true if the given instruction matches.
+ */
+ bool Matches(u16 instruction) const {
+ return (instruction & mask) == expected;
+ }
+
+ private:
+ const char* name;
+ u16 mask;
+ u16 expected;
+ Id id;
+ Type type;
+ };
+
+ static boost::optional<const Matcher&> Decode(Instruction instr) {
+ static const auto table{GetDecodeTable()};
+
+ const auto matches_instruction = [instr](const auto& matcher) {
+ return matcher.Matches(static_cast<u16>(instr.opcode));
+ };
+
+ auto iter = std::find_if(table.begin(), table.end(), matches_instruction);
+ return iter != table.end() ? boost::optional<const Matcher&>(*iter) : boost::none;
+ }
+
+private:
+ struct Detail {
+ private:
+ static constexpr size_t opcode_bitsize = 16;
+
+ /**
+ * Generates the mask and the expected value after masking from a given bitstring.
+ * A '0' in a bitstring indicates that a zero must be present at that bit position.
+ * A '1' in a bitstring indicates that a one must be present at that bit position.
+ */
+ static auto GetMaskAndExpect(const char* const bitstring) {
+ u16 mask = 0, expect = 0;
+ for (size_t i = 0; i < opcode_bitsize; i++) {
+ const size_t bit_position = opcode_bitsize - i - 1;
+ switch (bitstring[i]) {
+ case '0':
+ mask |= 1 << bit_position;
+ break;
+ case '1':
+ expect |= 1 << bit_position;
+ mask |= 1 << bit_position;
+ break;
+ default:
+ // Ignore
+ break;
+ }
+ }
+ return std::make_tuple(mask, expect);
+ }
+
+ public:
+ /// Creates a matcher that can match and parse instructions based on bitstring.
+ static auto GetMatcher(const char* const bitstring, OpCode::Id op, OpCode::Type type,
+ const char* const name) {
+ const auto mask_expect = GetMaskAndExpect(bitstring);
+ return Matcher(name, std::get<0>(mask_expect), std::get<1>(mask_expect), op, type);
+ }
+ };
+
+ static std::vector<Matcher> GetDecodeTable() {
+ std::vector<Matcher> table = {
+#define INST(bitstring, op, type, name) Detail::GetMatcher(bitstring, op, type, name)
+ INST("111000110011----", Id::KIL, Type::Flow, "KIL"),
+ INST("111000101001----", Id::SSY, Type::Flow, "SSY"),
+ INST("111000100100----", Id::BRA, Type::Flow, "BRA"),
+ INST("1110111111011---", Id::LD_A, Type::Memory, "LD_A"),
+ INST("1110111110010---", Id::LD_C, Type::Memory, "LD_C"),
+ INST("1110111111110---", Id::ST_A, Type::Memory, "ST_A"),
+ INST("1100000000111---", Id::TEX, Type::Memory, "TEX"),
+ INST("1101111101001---", Id::TEXQ, Type::Memory, "TEXQ"),
+ INST("1101100---------", Id::TEXS, Type::Memory, "TEXS"),
+ INST("1101101---------", Id::TLDS, Type::Memory, "TLDS"),
+ INST("111000110000----", Id::EXIT, Type::Trivial, "EXIT"),
+ INST("11100000--------", Id::IPA, Type::Trivial, "IPA"),
+ INST("001100101-------", Id::FFMA_IMM, Type::Ffma, "FFMA_IMM"),
+ INST("010010011-------", Id::FFMA_CR, Type::Ffma, "FFMA_CR"),
+ INST("010100011-------", Id::FFMA_RC, Type::Ffma, "FFMA_RC"),
+ INST("010110011-------", Id::FFMA_RR, Type::Ffma, "FFMA_RR"),
+ INST("0100110001011---", Id::FADD_C, Type::Arithmetic, "FADD_C"),
+ INST("0101110001011---", Id::FADD_R, Type::Arithmetic, "FADD_R"),
+ INST("0011100-01011---", Id::FADD_IMM, Type::Arithmetic, "FADD_IMM"),
+ INST("0100110001101---", Id::FMUL_C, Type::Arithmetic, "FMUL_C"),
+ INST("0101110001101---", Id::FMUL_R, Type::Arithmetic, "FMUL_R"),
+ INST("0011100-01101---", Id::FMUL_IMM, Type::Arithmetic, "FMUL_IMM"),
+ INST("00011110--------", Id::FMUL32_IMM, Type::Arithmetic, "FMUL32_IMM"),
+ INST("0100110000010---", Id::IADD_C, Type::ArithmeticInteger, "IADD_C"),
+ INST("0101110000010---", Id::IADD_R, Type::ArithmeticInteger, "IADD_R"),
+ INST("0011100-00010---", Id::IADD_IMM, Type::ArithmeticInteger, "IADD_IMM"),
+ INST("0001110---------", Id::IADD32I, Type::ArithmeticIntegerImmediate, "IADD32I"),
+ INST("0100110000011---", Id::ISCADD_C, Type::ArithmeticInteger, "ISCADD_C"),
+ INST("0101110000011---", Id::ISCADD_R, Type::ArithmeticInteger, "ISCADD_R"),
+ INST("0011100-00011---", Id::ISCADD_IMM, Type::ArithmeticInteger, "ISCADD_IMM"),
+ INST("0101000010000---", Id::MUFU, Type::Arithmetic, "MUFU"),
+ INST("0100110010010---", Id::RRO_C, Type::Arithmetic, "RRO_C"),
+ INST("0101110010010---", Id::RRO_R, Type::Arithmetic, "RRO_R"),
+ INST("0011100-10010---", Id::RRO_IMM, Type::Arithmetic, "RRO_IMM"),
+ INST("0100110010101---", Id::F2F_C, Type::Conversion, "F2F_C"),
+ INST("0101110010101---", Id::F2F_R, Type::Conversion, "F2F_R"),
+ INST("0011100-10101---", Id::F2F_IMM, Type::Conversion, "F2F_IMM"),
+ INST("0100110010110---", Id::F2I_C, Type::Conversion, "F2I_C"),
+ INST("0101110010110---", Id::F2I_R, Type::Conversion, "F2I_R"),
+ INST("0011100-10110---", Id::F2I_IMM, Type::Conversion, "F2I_IMM"),
+ INST("0100110010011---", Id::MOV_C, Type::Arithmetic, "MOV_C"),
+ INST("0101110010011---", Id::MOV_R, Type::Arithmetic, "MOV_R"),
+ INST("0011100-10011---", Id::MOV_IMM, Type::Arithmetic, "MOV_IMM"),
+ INST("000000010000----", Id::MOV32_IMM, Type::Arithmetic, "MOV32_IMM"),
+ INST("0100110001100---", Id::FMNMX_C, Type::Arithmetic, "FMNMX_C"),
+ INST("0101110001100---", Id::FMNMX_R, Type::Arithmetic, "FMNMX_R"),
+ INST("0011100-01100---", Id::FMNMX_IMM, Type::Arithmetic, "FMNMX_IMM"),
+ INST("0100110000100---", Id::IMNMX_C, Type::Arithmetic, "FMNMX_IMM"),
+ INST("0101110000100---", Id::IMNMX_R, Type::Arithmetic, "FMNMX_IMM"),
+ INST("0011100-00100---", Id::IMNMX_IMM, Type::Arithmetic, "FMNMX_IMM"),
+ INST("0100110000000---", Id::BFE_C, Type::Bfe, "BFE_C"),
+ INST("0101110000000---", Id::BFE_R, Type::Bfe, "BFE_R"),
+ INST("0011100-00000---", Id::BFE_IMM, Type::Bfe, "BFE_IMM"),
+ 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("000001----------", Id::LOP32I, Type::ArithmeticIntegerImmediate, "LOP32I"),
+ INST("0100110001001---", Id::SHL_C, Type::Shift, "SHL_C"),
+ INST("0101110001001---", Id::SHL_R, Type::Shift, "SHL_R"),
+ INST("0011100-01001---", Id::SHL_IMM, Type::Shift, "SHL_IMM"),
+ INST("0100110000101---", Id::SHR_C, Type::Shift, "SHR_C"),
+ INST("0101110000101---", Id::SHR_R, Type::Shift, "SHR_R"),
+ 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("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"),
+ INST("01011000--------", Id::FSET_R, Type::FloatSet, "FSET_R"),
+ INST("0100100---------", Id::FSET_C, Type::FloatSet, "FSET_C"),
+ INST("0011000---------", Id::FSET_IMM, Type::FloatSet, "FSET_IMM"),
+ INST("010010111011----", Id::FSETP_C, Type::FloatSetPredicate, "FSETP_C"),
+ INST("010110111011----", Id::FSETP_R, Type::FloatSetPredicate, "FSETP_R"),
+ INST("0011011-1011----", Id::FSETP_IMM, Type::FloatSetPredicate, "FSETP_IMM"),
+ INST("010010110110----", Id::ISETP_C, Type::IntegerSetPredicate, "ISETP_C"),
+ INST("010110110110----", Id::ISETP_R, Type::IntegerSetPredicate, "ISETP_R"),
+ INST("0011011-0110----", Id::ISETP_IMM, Type::IntegerSetPredicate, "ISETP_IMM"),
+ INST("010110110101----", Id::ISET_R, Type::IntegerSet, "ISET_R"),
+ INST("010010110101----", Id::ISET_C, Type::IntegerSet, "ISET_C"),
+ INST("0011011-0101----", Id::ISET_IMM, Type::IntegerSet, "ISET_IMM"),
+ INST("0101000010010---", Id::PSETP, Type::PredicateSetPredicate, "PSETP"),
+ INST("0011011-00------", Id::XMAD_IMM, Type::Arithmetic, "XMAD_IMM"),
+ INST("0100111---------", Id::XMAD_CR, Type::Arithmetic, "XMAD_CR"),
+ INST("010100010-------", Id::XMAD_RC, Type::Arithmetic, "XMAD_RC"),
+ INST("0101101100------", Id::XMAD_RR, Type::Arithmetic, "XMAD_RR"),
+ };
+#undef INST
+ std::stable_sort(table.begin(), table.end(), [](const auto& a, const auto& b) {
+ // If a matcher has more bits in its mask it is more specific, so it
+ // should come first.
+ return std::bitset<16>(a.GetMask()).count() > std::bitset<16>(b.GetMask()).count();
+ });
+
+ return table;
+ }
+};
+
+} // namespace Shader
+} // namespace Tegra
diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp
new file mode 100644
index 000000000..e36483145
--- /dev/null
+++ b/src/video_core/gpu.cpp
@@ -0,0 +1,43 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "video_core/engines/fermi_2d.h"
+#include "video_core/engines/maxwell_3d.h"
+#include "video_core/engines/maxwell_compute.h"
+#include "video_core/engines/maxwell_dma.h"
+#include "video_core/gpu.h"
+
+namespace Tegra {
+
+GPU::GPU() {
+ memory_manager = std::make_unique<MemoryManager>();
+ maxwell_3d = std::make_unique<Engines::Maxwell3D>(*memory_manager);
+ fermi_2d = std::make_unique<Engines::Fermi2D>(*memory_manager);
+ maxwell_compute = std::make_unique<Engines::MaxwellCompute>();
+ maxwell_dma = std::make_unique<Engines::MaxwellDMA>(*memory_manager);
+}
+
+GPU::~GPU() = default;
+
+const Tegra::Engines::Maxwell3D& GPU::Get3DEngine() const {
+ return *maxwell_3d;
+}
+
+u32 RenderTargetBytesPerPixel(RenderTargetFormat format) {
+ ASSERT(format != RenderTargetFormat::NONE);
+
+ switch (format) {
+ case RenderTargetFormat::RGBA32_FLOAT:
+ return 16;
+ case RenderTargetFormat::RGBA16_FLOAT:
+ return 8;
+ case RenderTargetFormat::RGBA8_UNORM:
+ case RenderTargetFormat::RGB10_A2_UNORM:
+ return 4;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented render target format {}", static_cast<u32>(format));
+ }
+}
+
+} // namespace Tegra
diff --git a/src/video_core/gpu.h b/src/video_core/gpu.h
index ba7781756..7b4e9b842 100644
--- a/src/video_core/gpu.h
+++ b/src/video_core/gpu.h
@@ -6,14 +6,66 @@
#include <memory>
#include <unordered_map>
+#include <vector>
#include "common/common_types.h"
-#include "video_core/engines/fermi_2d.h"
-#include "video_core/engines/maxwell_3d.h"
-#include "video_core/engines/maxwell_compute.h"
+#include "core/hle/service/nvflinger/buffer_queue.h"
#include "video_core/memory_manager.h"
namespace Tegra {
+enum class RenderTargetFormat : u32 {
+ NONE = 0x0,
+ RGBA32_FLOAT = 0xC0,
+ RGBA16_FLOAT = 0xCA,
+ RGB10_A2_UNORM = 0xD1,
+ RGBA8_UNORM = 0xD5,
+ RGBA8_SRGB = 0xD6,
+ R11G11B10_FLOAT = 0xE0,
+};
+
+/// Returns the number of bytes per pixel of each rendertarget format.
+u32 RenderTargetBytesPerPixel(RenderTargetFormat format);
+
+class DebugContext;
+
+/**
+ * Struct describing framebuffer configuration
+ */
+struct FramebufferConfig {
+ enum class PixelFormat : u32 {
+ ABGR8 = 1,
+ };
+
+ /**
+ * Returns the number of bytes per pixel.
+ */
+ static u32 BytesPerPixel(PixelFormat format) {
+ switch (format) {
+ case PixelFormat::ABGR8:
+ return 4;
+ }
+
+ UNREACHABLE();
+ }
+
+ VAddr address;
+ u32 offset;
+ u32 width;
+ u32 height;
+ u32 stride;
+ PixelFormat pixel_format;
+
+ using TransformFlags = Service::NVFlinger::BufferQueue::BufferTransformFlags;
+ TransformFlags transform_flags;
+};
+
+namespace Engines {
+class Fermi2D;
+class Maxwell3D;
+class MaxwellCompute;
+class MaxwellDMA;
+} // namespace Engines
+
enum class EngineID {
FERMI_TWOD_A = 0x902D, // 2D Engine
MAXWELL_B = 0xB197, // 3D Engine
@@ -24,22 +76,24 @@ enum class EngineID {
class GPU final {
public:
- GPU() {
- memory_manager = std::make_unique<MemoryManager>();
- maxwell_3d = std::make_unique<Engines::Maxwell3D>(*memory_manager);
- fermi_2d = std::make_unique<Engines::Fermi2D>();
- maxwell_compute = std::make_unique<Engines::MaxwellCompute>();
- }
- ~GPU() = default;
+ GPU();
+ ~GPU();
/// Processes a command list stored at the specified address in GPU memory.
void ProcessCommandList(GPUVAddr address, u32 size);
+ /// Returns a reference to the Maxwell3D GPU engine.
+ const Engines::Maxwell3D& Get3DEngine() const;
+
std::unique_ptr<MemoryManager> memory_manager;
+ Engines::Maxwell3D& Maxwell3D() {
+ return *maxwell_3d;
+ }
+
private:
/// Writes a single register in the engine bound to the specified subchannel
- void WriteReg(u32 method, u32 subchannel, u32 value);
+ void WriteReg(u32 method, u32 subchannel, u32 value, u32 remaining_params);
/// Mapping of command subchannels to their bound engine ids.
std::unordered_map<u32, EngineID> bound_engines;
@@ -50,6 +104,8 @@ private:
std::unique_ptr<Engines::Fermi2D> fermi_2d;
/// Compute engine
std::unique_ptr<Engines::MaxwellCompute> maxwell_compute;
+ /// DMA engine
+ std::unique_ptr<Engines::MaxwellDMA> maxwell_dma;
};
} // namespace Tegra
diff --git a/src/video_core/macro_interpreter.cpp b/src/video_core/macro_interpreter.cpp
new file mode 100644
index 000000000..44ece01c1
--- /dev/null
+++ b/src/video_core/macro_interpreter.cpp
@@ -0,0 +1,257 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/assert.h"
+#include "common/logging/log.h"
+#include "video_core/engines/maxwell_3d.h"
+#include "video_core/macro_interpreter.h"
+
+namespace Tegra {
+
+MacroInterpreter::MacroInterpreter(Engines::Maxwell3D& maxwell3d) : maxwell3d(maxwell3d) {}
+
+void MacroInterpreter::Execute(const std::vector<u32>& code, std::vector<u32> parameters) {
+ Reset();
+ registers[1] = parameters[0];
+ this->parameters = std::move(parameters);
+
+ // Execute the code until we hit an exit condition.
+ bool keep_executing = true;
+ while (keep_executing) {
+ keep_executing = Step(code, false);
+ }
+
+ // Assert the the macro used all the input parameters
+ ASSERT(next_parameter_index == this->parameters.size());
+}
+
+void MacroInterpreter::Reset() {
+ registers = {};
+ pc = 0;
+ delayed_pc = boost::none;
+ method_address.raw = 0;
+ parameters.clear();
+ // The next parameter index starts at 1, because $r1 already has the value of the first
+ // parameter.
+ next_parameter_index = 1;
+}
+
+bool MacroInterpreter::Step(const std::vector<u32>& code, bool is_delay_slot) {
+ u32 base_address = pc;
+
+ Opcode opcode = GetOpcode(code);
+ pc += 4;
+
+ // Update the program counter if we were delayed
+ if (delayed_pc != boost::none) {
+ ASSERT(is_delay_slot);
+ pc = *delayed_pc;
+ delayed_pc = boost::none;
+ }
+
+ switch (opcode.operation) {
+ case Operation::ALU: {
+ u32 result = GetALUResult(opcode.alu_operation, GetRegister(opcode.src_a),
+ GetRegister(opcode.src_b));
+ ProcessResult(opcode.result_operation, opcode.dst, result);
+ break;
+ }
+ case Operation::AddImmediate: {
+ ProcessResult(opcode.result_operation, opcode.dst,
+ GetRegister(opcode.src_a) + opcode.immediate);
+ break;
+ }
+ case Operation::ExtractInsert: {
+ u32 dst = GetRegister(opcode.src_a);
+ u32 src = GetRegister(opcode.src_b);
+
+ src = (src >> opcode.bf_src_bit) & opcode.GetBitfieldMask();
+ dst &= ~(opcode.GetBitfieldMask() << opcode.bf_dst_bit);
+ dst |= src << opcode.bf_dst_bit;
+ ProcessResult(opcode.result_operation, opcode.dst, dst);
+ break;
+ }
+ case Operation::ExtractShiftLeftImmediate: {
+ u32 dst = GetRegister(opcode.src_a);
+ u32 src = GetRegister(opcode.src_b);
+
+ u32 result = ((src >> dst) & opcode.GetBitfieldMask()) << opcode.bf_dst_bit;
+
+ ProcessResult(opcode.result_operation, opcode.dst, result);
+ break;
+ }
+ case Operation::ExtractShiftLeftRegister: {
+ u32 dst = GetRegister(opcode.src_a);
+ u32 src = GetRegister(opcode.src_b);
+
+ u32 result = ((src >> opcode.bf_src_bit) & opcode.GetBitfieldMask()) << dst;
+
+ ProcessResult(opcode.result_operation, opcode.dst, result);
+ break;
+ }
+ case Operation::Read: {
+ u32 result = Read(GetRegister(opcode.src_a) + opcode.immediate);
+ ProcessResult(opcode.result_operation, opcode.dst, result);
+ break;
+ }
+ case Operation::Branch: {
+ ASSERT_MSG(!is_delay_slot, "Executing a branch in a delay slot is not valid");
+ u32 value = GetRegister(opcode.src_a);
+ bool taken = EvaluateBranchCondition(opcode.branch_condition, value);
+ if (taken) {
+ // Ignore the delay slot if the branch has the annul bit.
+ if (opcode.branch_annul) {
+ pc = base_address + (opcode.immediate << 2);
+ return true;
+ }
+
+ delayed_pc = base_address + (opcode.immediate << 2);
+ // Execute one more instruction due to the delay slot.
+ return Step(code, true);
+ }
+ break;
+ }
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented macro operation {}",
+ static_cast<u32>(opcode.operation.Value()));
+ }
+
+ 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(code, true);
+ return false;
+ }
+
+ return true;
+}
+
+MacroInterpreter::Opcode MacroInterpreter::GetOpcode(const std::vector<u32>& code) const {
+ ASSERT((pc % sizeof(u32)) == 0);
+ ASSERT(pc < code.size() * sizeof(u32));
+ return {code[pc / sizeof(u32)]};
+}
+
+u32 MacroInterpreter::GetALUResult(ALUOperation operation, u32 src_a, u32 src_b) const {
+ switch (operation) {
+ case ALUOperation::Add:
+ return src_a + src_b;
+ // TODO(Subv): Implement AddWithCarry
+ case ALUOperation::Subtract:
+ return src_a - src_b;
+ // TODO(Subv): Implement SubtractWithBorrow
+ case ALUOperation::Xor:
+ return src_a ^ src_b;
+ case ALUOperation::Or:
+ return src_a | src_b;
+ case ALUOperation::And:
+ return src_a & src_b;
+ case ALUOperation::AndNot:
+ return src_a & ~src_b;
+ case ALUOperation::Nand:
+ return ~(src_a & src_b);
+
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented ALU operation {}", static_cast<u32>(operation));
+ }
+}
+
+void MacroInterpreter::ProcessResult(ResultOperation operation, u32 reg, u32 result) {
+ switch (operation) {
+ case ResultOperation::IgnoreAndFetch:
+ // Fetch parameter and ignore result.
+ SetRegister(reg, FetchParameter());
+ break;
+ case ResultOperation::Move:
+ // Move result.
+ SetRegister(reg, result);
+ break;
+ case ResultOperation::MoveAndSetMethod:
+ // Move result and use as Method Address.
+ SetRegister(reg, result);
+ SetMethodAddress(result);
+ break;
+ case ResultOperation::FetchAndSend:
+ // Fetch parameter and send result.
+ SetRegister(reg, FetchParameter());
+ Send(result);
+ break;
+ case ResultOperation::MoveAndSend:
+ // Move and send result.
+ SetRegister(reg, result);
+ Send(result);
+ break;
+ case ResultOperation::FetchAndSetMethod:
+ // Fetch parameter and use result as Method Address.
+ SetRegister(reg, FetchParameter());
+ SetMethodAddress(result);
+ break;
+ case ResultOperation::MoveAndSetMethodFetchAndSend:
+ // Move result and use as Method Address, then fetch and send parameter.
+ SetRegister(reg, result);
+ SetMethodAddress(result);
+ Send(FetchParameter());
+ break;
+ case ResultOperation::MoveAndSetMethodSend:
+ // Move result and use as Method Address, then send bits 12:17 of result.
+ SetRegister(reg, result);
+ SetMethodAddress(result);
+ Send((result >> 12) & 0b111111);
+ break;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented result operation {}", static_cast<u32>(operation));
+ }
+}
+
+u32 MacroInterpreter::FetchParameter() {
+ ASSERT(next_parameter_index < parameters.size());
+ return parameters[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];
+}
+
+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)
+ return;
+
+ ASSERT(register_id < registers.size());
+ registers[register_id] = value;
+}
+
+void MacroInterpreter::SetMethodAddress(u32 address) {
+ method_address.raw = address;
+}
+
+void MacroInterpreter::Send(u32 value) {
+ maxwell3d.WriteReg(method_address.address, value, 0);
+ // Increment the method address by the method increment.
+ method_address.address.Assign(method_address.address.Value() +
+ method_address.increment.Value());
+}
+
+u32 MacroInterpreter::Read(u32 method) const {
+ return maxwell3d.GetRegisterValue(method);
+}
+
+bool MacroInterpreter::EvaluateBranchCondition(BranchCondition cond, u32 value) const {
+ switch (cond) {
+ case BranchCondition::Zero:
+ return value == 0;
+ case BranchCondition::NotZero:
+ return value != 0;
+ }
+ UNREACHABLE();
+}
+
+} // namespace Tegra
diff --git a/src/video_core/macro_interpreter.h b/src/video_core/macro_interpreter.h
new file mode 100644
index 000000000..a71e359d8
--- /dev/null
+++ b/src/video_core/macro_interpreter.h
@@ -0,0 +1,164 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <vector>
+#include <boost/optional.hpp>
+#include "common/bit_field.h"
+#include "common/common_types.h"
+
+namespace Tegra {
+namespace Engines {
+class Maxwell3D;
+}
+
+class MacroInterpreter final {
+public:
+ explicit MacroInterpreter(Engines::Maxwell3D& maxwell3d);
+
+ /**
+ * Executes the macro code with the specified input parameters.
+ * @param code The macro byte code to execute
+ * @param parameters The parameters of the macro
+ */
+ void Execute(const std::vector<u32>& code, std::vector<u32> parameters);
+
+private:
+ enum class Operation : u32 {
+ ALU = 0,
+ AddImmediate = 1,
+ ExtractInsert = 2,
+ ExtractShiftLeftImmediate = 3,
+ ExtractShiftLeftRegister = 4,
+ Read = 5,
+ Unused = 6, // This operation doesn't seem to be a valid encoding.
+ Branch = 7,
+ };
+
+ enum class ALUOperation : u32 {
+ Add = 0,
+ AddWithCarry = 1,
+ Subtract = 2,
+ SubtractWithBorrow = 3,
+ // Operations 4-7 don't seem to be valid encodings.
+ Xor = 8,
+ Or = 9,
+ And = 10,
+ AndNot = 11,
+ Nand = 12
+ };
+
+ enum class ResultOperation : u32 {
+ IgnoreAndFetch = 0,
+ Move = 1,
+ MoveAndSetMethod = 2,
+ FetchAndSend = 3,
+ MoveAndSend = 4,
+ FetchAndSetMethod = 5,
+ MoveAndSetMethodFetchAndSend = 6,
+ MoveAndSetMethodSend = 7
+ };
+
+ enum class BranchCondition : u32 {
+ Zero = 0,
+ NotZero = 1,
+ };
+
+ union Opcode {
+ u32 raw;
+ BitField<0, 3, Operation> operation;
+ BitField<4, 3, ResultOperation> result_operation;
+ BitField<4, 1, BranchCondition> branch_condition;
+ BitField<5, 1, u32>
+ branch_annul; // If set on a branch, then the branch doesn't have a delay slot.
+ BitField<7, 1, u32> is_exit;
+ BitField<8, 3, u32> dst;
+ BitField<11, 3, u32> src_a;
+ BitField<14, 3, u32> src_b;
+ // The signed immediate overlaps the second source operand and the alu operation.
+ BitField<14, 18, s32> immediate;
+
+ BitField<17, 5, ALUOperation> alu_operation;
+
+ // Bitfield instructions data
+ BitField<17, 5, u32> bf_src_bit;
+ BitField<22, 5, u32> bf_size;
+ BitField<27, 5, u32> bf_dst_bit;
+
+ u32 GetBitfieldMask() const {
+ return (1 << bf_size) - 1;
+ }
+ };
+
+ union MethodAddress {
+ u32 raw;
+ BitField<0, 12, u32> address;
+ BitField<12, 6, u32> increment;
+ };
+
+ /// Resets the execution engine state, zeroing registers, etc.
+ void Reset();
+
+ /**
+ * Executes a single macro instruction located at the current program counter. Returns whether
+ * the interpreter should keep running.
+ * @param code The macro code to execute.
+ * @param is_delay_slot Whether the current step is being executed due to a delay slot in a
+ * previous instruction.
+ */
+ bool Step(const std::vector<u32>& code, bool is_delay_slot);
+
+ /// Calculates the result of an ALU operation. src_a OP src_b;
+ u32 GetALUResult(ALUOperation operation, u32 src_a, u32 src_b) const;
+
+ /// Performs the result operation on the input result and stores it in the specified register
+ /// (if necessary).
+ void ProcessResult(ResultOperation operation, u32 reg, u32 result);
+
+ /// Evaluates the branch condition and returns whether the branch should be taken or not.
+ bool EvaluateBranchCondition(BranchCondition cond, u32 value) const;
+
+ /// Reads an opcode at the current program counter location.
+ Opcode GetOpcode(const std::vector<u32>& code) const;
+
+ /// Returns the specified register's value. Register 0 is hardcoded to always return 0.
+ u32 GetRegister(u32 register_id) const;
+
+ /// Sets the register to the input value.
+ void SetRegister(u32 register_id, u32 value);
+
+ /// Sets the method address to use for the next Send instruction.
+ void SetMethodAddress(u32 address);
+
+ /// Calls a GPU Engine method with the input parameter.
+ void Send(u32 value);
+
+ /// Reads a GPU register located at the method address.
+ u32 Read(u32 method) const;
+
+ /// Returns the next parameter in the parameter queue.
+ u32 FetchParameter();
+
+ Engines::Maxwell3D& maxwell3d;
+
+ u32 pc; ///< Current program counter
+ boost::optional<u32>
+ delayed_pc; ///< Program counter to execute at after the delay slot is executed.
+
+ static constexpr size_t NumMacroRegisters = 8;
+
+ /// General purpose macro registers.
+ std::array<u32, NumMacroRegisters> registers = {};
+
+ /// Method address to use for the next Send instruction.
+ MethodAddress method_address = {};
+
+ /// Input parameters of the current macro.
+ std::vector<u32> parameters;
+ /// Index of the next parameter that will be fetched by the 'parm' instruction.
+ u32 next_parameter_index = 0;
+};
+} // namespace Tegra
diff --git a/src/video_core/memory_manager.cpp b/src/video_core/memory_manager.cpp
index 2789a4ca1..5cefce9fc 100644
--- a/src/video_core/memory_manager.cpp
+++ b/src/video_core/memory_manager.cpp
@@ -2,109 +2,137 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include "common/alignment.h"
#include "common/assert.h"
#include "video_core/memory_manager.h"
namespace Tegra {
-PAddr MemoryManager::AllocateSpace(u64 size, u64 align) {
- boost::optional<PAddr> paddr = FindFreeBlock(size, align);
- ASSERT(paddr);
+GPUVAddr MemoryManager::AllocateSpace(u64 size, u64 align) {
+ boost::optional<GPUVAddr> gpu_addr = FindFreeBlock(size, align);
+ ASSERT(gpu_addr);
- for (u64 offset = 0; offset < size; offset += Memory::PAGE_SIZE) {
- PageSlot(*paddr + offset) = static_cast<u64>(PageStatus::Allocated);
+ for (u64 offset = 0; offset < size; offset += PAGE_SIZE) {
+ ASSERT(PageSlot(*gpu_addr + offset) == static_cast<u64>(PageStatus::Unmapped));
+ PageSlot(*gpu_addr + offset) = static_cast<u64>(PageStatus::Allocated);
}
- return *paddr;
+ return *gpu_addr;
}
-PAddr MemoryManager::AllocateSpace(PAddr paddr, u64 size, u64 align) {
- for (u64 offset = 0; offset < size; offset += Memory::PAGE_SIZE) {
- if (IsPageMapped(paddr + offset)) {
- return AllocateSpace(size, align);
- }
- }
-
- for (u64 offset = 0; offset < size; offset += Memory::PAGE_SIZE) {
- PageSlot(paddr + offset) = static_cast<u64>(PageStatus::Allocated);
+GPUVAddr MemoryManager::AllocateSpace(GPUVAddr gpu_addr, u64 size, u64 align) {
+ for (u64 offset = 0; offset < size; offset += PAGE_SIZE) {
+ ASSERT(PageSlot(gpu_addr + offset) == static_cast<u64>(PageStatus::Unmapped));
+ PageSlot(gpu_addr + offset) = static_cast<u64>(PageStatus::Allocated);
}
- return paddr;
+ return gpu_addr;
}
-PAddr MemoryManager::MapBufferEx(VAddr vaddr, u64 size) {
- vaddr &= ~Memory::PAGE_MASK;
+GPUVAddr MemoryManager::MapBufferEx(VAddr cpu_addr, u64 size) {
+ boost::optional<GPUVAddr> gpu_addr = FindFreeBlock(size, PAGE_SIZE);
+ ASSERT(gpu_addr);
- boost::optional<PAddr> paddr = FindFreeBlock(size);
- ASSERT(paddr);
-
- for (u64 offset = 0; offset < size; offset += Memory::PAGE_SIZE) {
- PageSlot(*paddr + offset) = vaddr + offset;
+ for (u64 offset = 0; offset < size; offset += PAGE_SIZE) {
+ ASSERT(PageSlot(*gpu_addr + offset) == static_cast<u64>(PageStatus::Unmapped));
+ PageSlot(*gpu_addr + offset) = cpu_addr + offset;
}
- return *paddr;
+ MappedRegion region{cpu_addr, *gpu_addr, size};
+ mapped_regions.push_back(region);
+
+ return *gpu_addr;
}
-PAddr MemoryManager::MapBufferEx(VAddr vaddr, PAddr paddr, u64 size) {
- vaddr &= ~Memory::PAGE_MASK;
- paddr &= ~Memory::PAGE_MASK;
+GPUVAddr MemoryManager::MapBufferEx(VAddr cpu_addr, GPUVAddr gpu_addr, u64 size) {
+ ASSERT((gpu_addr & PAGE_MASK) == 0);
- for (u64 offset = 0; offset < size; offset += Memory::PAGE_SIZE) {
- if (PageSlot(paddr + offset) != static_cast<u64>(PageStatus::Allocated)) {
- return MapBufferEx(vaddr, size);
- }
+ for (u64 offset = 0; offset < size; offset += PAGE_SIZE) {
+ ASSERT(PageSlot(gpu_addr + offset) == static_cast<u64>(PageStatus::Allocated));
+ PageSlot(gpu_addr + offset) = cpu_addr + offset;
}
- for (u64 offset = 0; offset < size; offset += Memory::PAGE_SIZE) {
- PageSlot(paddr + offset) = vaddr + offset;
+ MappedRegion region{cpu_addr, gpu_addr, size};
+ mapped_regions.push_back(region);
+
+ return gpu_addr;
+}
+
+GPUVAddr MemoryManager::UnmapBuffer(GPUVAddr gpu_addr, u64 size) {
+ ASSERT((gpu_addr & PAGE_MASK) == 0);
+
+ for (u64 offset = 0; offset < size; offset += PAGE_SIZE) {
+ ASSERT(PageSlot(gpu_addr + offset) != static_cast<u64>(PageStatus::Allocated) &&
+ PageSlot(gpu_addr + offset) != static_cast<u64>(PageStatus::Unmapped));
+ PageSlot(gpu_addr + offset) = static_cast<u64>(PageStatus::Unmapped);
}
- return paddr;
+ // 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;
}
-boost::optional<PAddr> MemoryManager::FindFreeBlock(u64 size, u64 align) {
- PAddr paddr{};
- u64 free_space{};
- align = (align + Memory::PAGE_MASK) & ~Memory::PAGE_MASK;
+boost::optional<GPUVAddr> MemoryManager::FindFreeBlock(u64 size, u64 align) {
+ GPUVAddr gpu_addr = 0;
+ u64 free_space = 0;
+ align = (align + PAGE_MASK) & ~PAGE_MASK;
- while (paddr + free_space < MAX_ADDRESS) {
- if (!IsPageMapped(paddr + free_space)) {
- free_space += Memory::PAGE_SIZE;
+ while (gpu_addr + free_space < MAX_ADDRESS) {
+ if (!IsPageMapped(gpu_addr + free_space)) {
+ free_space += PAGE_SIZE;
if (free_space >= size) {
- return paddr;
+ return gpu_addr;
}
} else {
- paddr += free_space + Memory::PAGE_SIZE;
+ gpu_addr += free_space + PAGE_SIZE;
free_space = 0;
- const u64 remainder{paddr % align};
- if (!remainder) {
- paddr = (paddr - remainder) + align;
- }
+ gpu_addr = Common::AlignUp(gpu_addr, align);
}
}
return {};
}
-VAddr MemoryManager::PhysicalToVirtualAddress(PAddr paddr) {
- VAddr base_addr = PageSlot(paddr);
+boost::optional<VAddr> MemoryManager::GpuToCpuAddress(GPUVAddr gpu_addr) {
+ VAddr base_addr = PageSlot(gpu_addr);
ASSERT(base_addr != static_cast<u64>(PageStatus::Unmapped));
- return base_addr + (paddr & Memory::PAGE_MASK);
+
+ if (base_addr == static_cast<u64>(PageStatus::Allocated)) {
+ return {};
+ }
+
+ return base_addr + (gpu_addr & PAGE_MASK);
+}
+
+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)) {
+ u64 offset = cpu_addr - region.cpu_addr;
+ results.push_back(region.gpu_addr + offset);
+ }
+ }
+ return results;
}
-bool MemoryManager::IsPageMapped(PAddr paddr) {
- return PageSlot(paddr) != static_cast<u64>(PageStatus::Unmapped);
+bool MemoryManager::IsPageMapped(GPUVAddr gpu_addr) {
+ return PageSlot(gpu_addr) != static_cast<u64>(PageStatus::Unmapped);
}
-VAddr& MemoryManager::PageSlot(PAddr paddr) {
- auto& block = page_table[(paddr >> (Memory::PAGE_BITS + PAGE_TABLE_BITS)) & PAGE_TABLE_MASK];
+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>();
for (unsigned index = 0; index < PAGE_BLOCK_SIZE; index++) {
(*block)[index] = static_cast<u64>(PageStatus::Unmapped);
}
}
- return (*block)[(paddr >> Memory::PAGE_BITS) & PAGE_BLOCK_MASK];
+ 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 47da7acd6..86765e72a 100644
--- a/src/video_core/memory_manager.h
+++ b/src/video_core/memory_manager.h
@@ -6,8 +6,11 @@
#include <array>
#include <memory>
+#include <vector>
+
+#include <boost/optional.hpp>
+
#include "common/common_types.h"
-#include "core/memory.h"
namespace Tegra {
@@ -18,16 +21,22 @@ class MemoryManager final {
public:
MemoryManager() = default;
- PAddr AllocateSpace(u64 size, u64 align);
- PAddr AllocateSpace(PAddr paddr, u64 size, u64 align);
- PAddr MapBufferEx(VAddr vaddr, u64 size);
- PAddr MapBufferEx(VAddr vaddr, PAddr paddr, u64 size);
- VAddr PhysicalToVirtualAddress(PAddr paddr);
+ GPUVAddr AllocateSpace(u64 size, u64 align);
+ GPUVAddr AllocateSpace(GPUVAddr gpu_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);
+ boost::optional<VAddr> GpuToCpuAddress(GPUVAddr gpu_addr);
+ std::vector<GPUVAddr> CpuToGpuAddress(VAddr cpu_addr) const;
+
+ static constexpr u64 PAGE_BITS = 16;
+ static constexpr u64 PAGE_SIZE = 1 << PAGE_BITS;
+ static constexpr u64 PAGE_MASK = PAGE_SIZE - 1;
private:
- boost::optional<PAddr> FindFreeBlock(u64 size, u64 align = 1);
- bool IsPageMapped(PAddr paddr);
- VAddr& PageSlot(PAddr paddr);
+ boost::optional<GPUVAddr> FindFreeBlock(u64 size, u64 align = 1);
+ bool IsPageMapped(GPUVAddr gpu_addr);
+ VAddr& PageSlot(GPUVAddr gpu_addr);
enum class PageStatus : u64 {
Unmapped = 0xFFFFFFFFFFFFFFFFULL,
@@ -35,7 +44,7 @@ private:
};
static constexpr u64 MAX_ADDRESS{0x10000000000ULL};
- static constexpr u64 PAGE_TABLE_BITS{14};
+ 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};
@@ -44,6 +53,14 @@ private:
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;
+ };
+
+ std::vector<MappedRegion> mapped_regions;
};
} // namespace Tegra
diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h
new file mode 100644
index 000000000..f0e48a802
--- /dev/null
+++ b/src/video_core/rasterizer_interface.h
@@ -0,0 +1,64 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/common_types.h"
+#include "video_core/gpu.h"
+#include "video_core/memory_manager.h"
+
+struct ScreenInfo;
+
+namespace VideoCore {
+
+class RasterizerInterface {
+public:
+ virtual ~RasterizerInterface() {}
+
+ /// Draw the current batch of vertex arrays
+ virtual void DrawArrays() = 0;
+
+ /// Notify rasterizer that the specified Maxwell register has been changed
+ virtual void NotifyMaxwellRegisterChanged(u32 method) = 0;
+
+ /// Notify rasterizer that all caches should be flushed to Switch memory
+ virtual void FlushAll() = 0;
+
+ /// Notify rasterizer that any caches of the specified region should be flushed to Switch memory
+ virtual void FlushRegion(Tegra::GPUVAddr addr, u64 size) = 0;
+
+ /// Notify rasterizer that any caches of the specified region should be invalidated
+ virtual void InvalidateRegion(Tegra::GPUVAddr addr, u64 size) = 0;
+
+ /// Notify rasterizer that any caches of the specified region should be flushed to Switch memory
+ /// and invalidated
+ virtual void FlushAndInvalidateRegion(Tegra::GPUVAddr addr, u64 size) = 0;
+
+ /// Attempt to use a faster method to perform a display transfer with is_texture_copy = 0
+ virtual bool AccelerateDisplayTransfer(const void* config) {
+ return false;
+ }
+
+ /// Attempt to use a faster method to perform a display transfer with is_texture_copy = 1
+ virtual bool AccelerateTextureCopy(const void* config) {
+ return false;
+ }
+
+ /// Attempt to use a faster method to fill a region
+ virtual bool AccelerateFill(const void* config) {
+ return false;
+ }
+
+ /// Attempt to use a faster method to display the framebuffer to screen
+ virtual bool AccelerateDisplay(const Tegra::FramebufferConfig& framebuffer,
+ VAddr framebuffer_addr, u32 pixel_stride,
+ ScreenInfo& screen_info) {
+ return false;
+ }
+
+ virtual bool AccelerateDrawBatch(bool is_indexed) {
+ return false;
+ }
+};
+} // namespace VideoCore
diff --git a/src/video_core/renderer_base.cpp b/src/video_core/renderer_base.cpp
index 51e1d45f9..30075b23c 100644
--- a/src/video_core/renderer_base.cpp
+++ b/src/video_core/renderer_base.cpp
@@ -5,6 +5,11 @@
#include <atomic>
#include <memory>
#include "video_core/renderer_base.h"
+#include "video_core/renderer_opengl/gl_rasterizer.h"
#include "video_core/video_core.h"
-void RendererBase::RefreshRasterizerSetting() {}
+void RendererBase::RefreshRasterizerSetting() {
+ if (rasterizer == nullptr) {
+ rasterizer = std::make_unique<RasterizerOpenGL>();
+ }
+}
diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h
index 2aba50eda..89a960eaf 100644
--- a/src/video_core/renderer_base.h
+++ b/src/video_core/renderer_base.h
@@ -8,6 +8,8 @@
#include <boost/optional.hpp>
#include "common/assert.h"
#include "common/common_types.h"
+#include "video_core/gpu.h"
+#include "video_core/rasterizer_interface.h"
class EmuWindow;
@@ -16,40 +18,10 @@ public:
/// Used to reference a framebuffer
enum kFramebuffer { kFramebuffer_VirtualXFB = 0, kFramebuffer_EFB, kFramebuffer_Texture };
- /**
- * Struct describing framebuffer metadata
- * TODO(bunnei): This struct belongs in the GPU code, but we don't have a good place for it yet.
- */
- struct FramebufferInfo {
- enum class PixelFormat : u32 {
- ABGR8 = 1,
- };
-
- /**
- * Returns the number of bytes per pixel.
- */
- static u32 BytesPerPixel(PixelFormat format) {
- switch (format) {
- case PixelFormat::ABGR8:
- return 4;
- }
-
- UNREACHABLE();
- }
-
- VAddr address;
- u32 offset;
- u32 width;
- u32 height;
- u32 stride;
- PixelFormat pixel_format;
- bool flip_vertical;
- };
-
virtual ~RendererBase() {}
/// Swap buffers (render frame)
- virtual void SwapBuffers(boost::optional<const FramebufferInfo&> framebuffer_info) = 0;
+ virtual void SwapBuffers(boost::optional<const Tegra::FramebufferConfig&> framebuffer) = 0;
/**
* Set the emulator window to use for renderer
@@ -74,12 +46,16 @@ public:
return m_current_frame;
}
+ VideoCore::RasterizerInterface* Rasterizer() const {
+ return rasterizer.get();
+ }
+
void RefreshRasterizerSetting();
protected:
+ std::unique_ptr<VideoCore::RasterizerInterface> rasterizer;
f32 m_current_fps = 0.0f; ///< Current framerate, should be set by the renderer
int m_current_frame = 0; ///< Current frame, should be set by the renderer
private:
- bool opengl_rasterizer_active = false;
};
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
new file mode 100644
index 000000000..833ff8273
--- /dev/null
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -0,0 +1,758 @@
+// Copyright 2015 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <memory>
+#include <string>
+#include <tuple>
+#include <utility>
+#include <glad/glad.h>
+#include "common/alignment.h"
+#include "common/assert.h"
+#include "common/logging/log.h"
+#include "common/math_util.h"
+#include "common/microprofile.h"
+#include "common/scope_exit.h"
+#include "core/core.h"
+#include "core/hle/kernel/process.h"
+#include "core/settings.h"
+#include "video_core/engines/maxwell_3d.h"
+#include "video_core/renderer_opengl/gl_rasterizer.h"
+#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"
+
+using Maxwell = Tegra::Engines::Maxwell3D::Regs;
+using PixelFormat = SurfaceParams::PixelFormat;
+using SurfaceType = SurfaceParams::SurfaceType;
+
+MICROPROFILE_DEFINE(OpenGL_VAO, "OpenGL", "Vertex Array Setup", MP_RGB(128, 128, 192));
+MICROPROFILE_DEFINE(OpenGL_VS, "OpenGL", "Vertex Shader Setup", MP_RGB(128, 128, 192));
+MICROPROFILE_DEFINE(OpenGL_FS, "OpenGL", "Fragment Shader Setup", MP_RGB(128, 128, 192));
+MICROPROFILE_DEFINE(OpenGL_Drawing, "OpenGL", "Drawing", MP_RGB(128, 128, 192));
+MICROPROFILE_DEFINE(OpenGL_Blits, "OpenGL", "Blits", MP_RGB(100, 100, 255));
+MICROPROFILE_DEFINE(OpenGL_CacheManagement, "OpenGL", "Cache Mgmt", MP_RGB(100, 255, 100));
+
+RasterizerOpenGL::RasterizerOpenGL() {
+ has_ARB_buffer_storage = false;
+ has_ARB_direct_state_access = false;
+ has_ARB_separate_shader_objects = false;
+ has_ARB_vertex_attrib_binding = false;
+
+ // Create sampler objects
+ for (size_t i = 0; i < texture_samplers.size(); ++i) {
+ texture_samplers[i].Create();
+ state.texture_units[i].sampler = texture_samplers[i].sampler.handle;
+ }
+
+ // Create SSBOs
+ for (size_t stage = 0; stage < ssbos.size(); ++stage) {
+ for (size_t buffer = 0; buffer < ssbos[stage].size(); ++buffer) {
+ ssbos[stage][buffer].Create();
+ state.draw.const_buffers[stage][buffer].ssbo = ssbos[stage][buffer].handle;
+ }
+ }
+
+ GLint ext_num;
+ glGetIntegerv(GL_NUM_EXTENSIONS, &ext_num);
+ for (GLint i = 0; i < ext_num; i++) {
+ std::string extension{reinterpret_cast<const char*>(glGetStringi(GL_EXTENSIONS, i))};
+
+ if (extension == "GL_ARB_buffer_storage") {
+ has_ARB_buffer_storage = true;
+ } else if (extension == "GL_ARB_direct_state_access") {
+ has_ARB_direct_state_access = true;
+ } else if (extension == "GL_ARB_separate_shader_objects") {
+ has_ARB_separate_shader_objects = true;
+ } else if (extension == "GL_ARB_vertex_attrib_binding") {
+ has_ARB_vertex_attrib_binding = true;
+ }
+ }
+
+ ASSERT_MSG(has_ARB_separate_shader_objects, "has_ARB_separate_shader_objects is unsupported");
+
+ // Clipping plane 0 is always enabled for PICA fixed clip plane z <= 0
+ state.clip_distance[0] = true;
+
+ // Generate VAO and UBO
+ sw_vao.Create();
+ uniform_buffer.Create();
+
+ state.draw.vertex_array = sw_vao.handle;
+ state.draw.uniform_buffer = uniform_buffer.handle;
+ state.Apply();
+
+ // Create render framebuffer
+ framebuffer.Create();
+
+ hw_vao.Create();
+
+ stream_buffer = OGLStreamBuffer::MakeBuffer(has_ARB_buffer_storage, GL_ARRAY_BUFFER);
+ stream_buffer->Create(STREAM_BUFFER_SIZE, STREAM_BUFFER_SIZE / 2);
+ state.draw.vertex_buffer = stream_buffer->GetHandle();
+
+ shader_program_manager = std::make_unique<GLShader::ProgramManager>();
+ state.draw.shader_program = 0;
+ state.draw.vertex_array = hw_vao.handle;
+ state.Apply();
+
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, stream_buffer->GetHandle());
+
+ for (unsigned index = 0; index < uniform_buffers.size(); ++index) {
+ auto& buffer = uniform_buffers[index];
+ buffer.Create();
+ glBindBuffer(GL_UNIFORM_BUFFER, buffer.handle);
+ glBufferData(GL_UNIFORM_BUFFER, sizeof(GLShader::MaxwellUniformData), nullptr,
+ GL_STREAM_COPY);
+ glBindBufferBase(GL_UNIFORM_BUFFER, index, buffer.handle);
+ }
+
+ accelerate_draw = AccelDraw::Disabled;
+
+ glEnable(GL_BLEND);
+
+ NGLOG_CRITICAL(Render_OpenGL, "Sync fixed function OpenGL state here!");
+}
+
+RasterizerOpenGL::~RasterizerOpenGL() {
+ if (stream_buffer != nullptr) {
+ state.draw.vertex_buffer = stream_buffer->GetHandle();
+ state.Apply();
+ stream_buffer->Release();
+ }
+}
+
+std::pair<u8*, GLintptr> RasterizerOpenGL::SetupVertexArrays(u8* array_ptr,
+ GLintptr buffer_offset) {
+ MICROPROFILE_SCOPE(OpenGL_VAO);
+ const auto& regs = Core::System().GetInstance().GPU().Maxwell3D().regs;
+ const auto& memory_manager = Core::System().GetInstance().GPU().memory_manager;
+
+ state.draw.vertex_array = hw_vao.handle;
+ state.draw.vertex_buffer = stream_buffer->GetHandle();
+ state.Apply();
+
+ // Upload all guest vertex arrays sequentially to our buffer
+ for (u32 index = 0; index < Maxwell::NumVertexArrays; ++index) {
+ const auto& vertex_array = regs.vertex_array[index];
+ if (!vertex_array.IsEnabled())
+ continue;
+
+ const Tegra::GPUVAddr start = vertex_array.StartAddress();
+ const Tegra::GPUVAddr end = regs.vertex_array_limit[index].LimitAddress();
+
+ ASSERT(end > start);
+ u64 size = end - start + 1;
+
+ // Copy vertex array data
+ res_cache.FlushRegion(start, size, nullptr);
+ Memory::ReadBlock(*memory_manager->GpuToCpuAddress(start), array_ptr, size);
+
+ // Bind the vertex array to the buffer at the current offset.
+ glBindVertexBuffer(index, stream_buffer->GetHandle(), buffer_offset, vertex_array.stride);
+
+ ASSERT_MSG(vertex_array.divisor == 0, "Vertex buffer divisor unimplemented");
+
+ array_ptr += size;
+ buffer_offset += size;
+ }
+
+ // Use the vertex array as-is, assumes that the data is formatted correctly for OpenGL.
+ // Enables the first 16 vertex attributes always, as we don't know which ones are actually used
+ // until shader time. Note, Tegra technically supports 32, but we're capping this to 16 for now
+ // to avoid OpenGL errors.
+ // TODO(Subv): Analyze the shader to identify which attributes are actually used and don't
+ // assume every shader uses them all.
+ for (unsigned index = 0; index < 16; ++index) {
+ auto& attrib = regs.vertex_attrib_format[index];
+ NGLOG_DEBUG(HW_GPU, "vertex attrib {}, count={}, size={}, type={}, offset={}, normalize={}",
+ index, attrib.ComponentCount(), attrib.SizeString(), attrib.TypeString(),
+ attrib.offset.Value(), attrib.IsNormalized());
+
+ auto& buffer = regs.vertex_array[attrib.buffer];
+ ASSERT(buffer.IsEnabled());
+
+ glEnableVertexAttribArray(index);
+ glVertexAttribFormat(index, attrib.ComponentCount(), MaxwellToGL::VertexType(attrib),
+ attrib.IsNormalized() ? GL_TRUE : GL_FALSE, attrib.offset);
+ glVertexAttribBinding(index, attrib.buffer);
+ }
+
+ return {array_ptr, buffer_offset};
+}
+
+void RasterizerOpenGL::SetupShaders(u8* buffer_ptr, GLintptr buffer_offset) {
+ // Helper function for uploading uniform data
+ const auto copy_buffer = [&](GLuint handle, GLintptr offset, GLsizeiptr size) {
+ if (has_ARB_direct_state_access) {
+ glCopyNamedBufferSubData(stream_buffer->GetHandle(), handle, offset, 0, size);
+ } else {
+ glBindBuffer(GL_COPY_WRITE_BUFFER, handle);
+ glCopyBufferSubData(GL_ARRAY_BUFFER, GL_COPY_WRITE_BUFFER, offset, 0, size);
+ }
+ };
+
+ auto& gpu = Core::System().GetInstance().GPU().Maxwell3D();
+ ASSERT_MSG(!gpu.regs.shader_config[0].enable, "VertexA is unsupported!");
+
+ // Next available bindpoints to use when uploading the const buffers and textures to the GLSL
+ // shaders.
+ u32 current_constbuffer_bindpoint = 0;
+ u32 current_texture_bindpoint = 0;
+
+ for (unsigned index = 1; index < Maxwell::MaxShaderProgram; ++index) {
+ auto& shader_config = gpu.regs.shader_config[index];
+ const Maxwell::ShaderProgram program{static_cast<Maxwell::ShaderProgram>(index)};
+
+ const auto& stage = index - 1; // Stage indices are 0 - 5
+
+ const bool is_enabled = gpu.IsShaderStageEnabled(static_cast<Maxwell::ShaderStage>(stage));
+
+ // Skip stages that are not enabled
+ if (!is_enabled) {
+ continue;
+ }
+
+ GLShader::MaxwellUniformData ubo{};
+ ubo.SetFromRegs(gpu.state.shader_stages[stage]);
+ std::memcpy(buffer_ptr, &ubo, sizeof(ubo));
+
+ // Flush the buffer so that the GPU can see the data we just wrote.
+ glFlushMappedBufferRange(GL_ARRAY_BUFFER, buffer_offset, sizeof(ubo));
+
+ // Upload uniform data as one UBO per stage
+ const GLintptr ubo_offset = buffer_offset;
+ copy_buffer(uniform_buffers[stage].handle, ubo_offset,
+ sizeof(GLShader::MaxwellUniformData));
+
+ buffer_ptr += sizeof(GLShader::MaxwellUniformData);
+ buffer_offset += sizeof(GLShader::MaxwellUniformData);
+
+ // Fetch program code from memory
+ GLShader::ProgramCode program_code;
+ const u64 gpu_address{gpu.regs.code_address.CodeAddress() + shader_config.offset};
+ const boost::optional<VAddr> cpu_address{gpu.memory_manager.GpuToCpuAddress(gpu_address)};
+ Memory::ReadBlock(*cpu_address, program_code.data(), program_code.size() * sizeof(u64));
+ GLShader::ShaderSetup setup{std::move(program_code)};
+
+ GLShader::ShaderEntries shader_resources;
+
+ switch (program) {
+ case Maxwell::ShaderProgram::VertexB: {
+ GLShader::MaxwellVSConfig vs_config{setup};
+ shader_resources =
+ shader_program_manager->UseProgrammableVertexShader(vs_config, setup);
+ break;
+ }
+ case Maxwell::ShaderProgram::Fragment: {
+ GLShader::MaxwellFSConfig fs_config{setup};
+ shader_resources =
+ shader_program_manager->UseProgrammableFragmentShader(fs_config, setup);
+ break;
+ }
+ default:
+ NGLOG_CRITICAL(HW_GPU, "Unimplemented shader index={}, enable={}, offset=0x{:08X}",
+ index, shader_config.enable.Value(), shader_config.offset);
+ UNREACHABLE();
+ }
+
+ GLuint gl_stage_program = shader_program_manager->GetCurrentProgramStage(
+ static_cast<Maxwell::ShaderStage>(stage));
+
+ // Configure the const buffers for this shader stage.
+ current_constbuffer_bindpoint =
+ SetupConstBuffers(static_cast<Maxwell::ShaderStage>(stage), gl_stage_program,
+ current_constbuffer_bindpoint, shader_resources.const_buffer_entries);
+
+ // Configure the textures for this shader stage.
+ current_texture_bindpoint =
+ SetupTextures(static_cast<Maxwell::ShaderStage>(stage), gl_stage_program,
+ current_texture_bindpoint, shader_resources.texture_samplers);
+ }
+
+ shader_program_manager->UseTrivialGeometryShader();
+}
+
+size_t RasterizerOpenGL::CalculateVertexArraysSize() const {
+ const auto& regs = Core::System().GetInstance().GPU().Maxwell3D().regs;
+
+ 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();
+
+ ASSERT(end > start);
+ size += end - start + 1;
+ }
+
+ return size;
+}
+
+bool RasterizerOpenGL::AccelerateDrawBatch(bool is_indexed) {
+ accelerate_draw = is_indexed ? AccelDraw::Indexed : AccelDraw::Arrays;
+ DrawArrays();
+ return true;
+}
+
+void RasterizerOpenGL::DrawArrays() {
+ if (accelerate_draw == AccelDraw::Disabled)
+ return;
+
+ MICROPROFILE_SCOPE(OpenGL_Drawing);
+ const auto& regs = Core::System().GetInstance().GPU().Maxwell3D().regs;
+
+ // TODO(bunnei): Implement these
+ const bool has_stencil = false;
+ const bool using_color_fb = true;
+ const bool using_depth_fb = false;
+ const MathUtil::Rectangle<s32> viewport_rect{regs.viewport_transform[0].GetRect()};
+
+ const bool write_color_fb =
+ state.color_mask.red_enabled == GL_TRUE || state.color_mask.green_enabled == GL_TRUE ||
+ state.color_mask.blue_enabled == GL_TRUE || state.color_mask.alpha_enabled == GL_TRUE;
+
+ const bool write_depth_fb =
+ (state.depth.test_enabled && state.depth.write_mask == GL_TRUE) ||
+ (has_stencil && state.stencil.test_enabled && state.stencil.write_mask != 0);
+
+ Surface color_surface;
+ Surface depth_surface;
+ MathUtil::Rectangle<u32> surfaces_rect;
+ std::tie(color_surface, depth_surface, surfaces_rect) =
+ res_cache.GetFramebufferSurfaces(using_color_fb, using_depth_fb, viewport_rect);
+
+ const u16 res_scale = color_surface != nullptr
+ ? color_surface->res_scale
+ : (depth_surface == nullptr ? 1u : depth_surface->res_scale);
+
+ MathUtil::Rectangle<u32> draw_rect{
+ static_cast<u32>(
+ std::clamp<s32>(static_cast<s32>(surfaces_rect.left) + viewport_rect.left * res_scale,
+ surfaces_rect.left, surfaces_rect.right)), // Left
+ static_cast<u32>(
+ std::clamp<s32>(static_cast<s32>(surfaces_rect.bottom) + viewport_rect.top * res_scale,
+ surfaces_rect.bottom, surfaces_rect.top)), // Top
+ static_cast<u32>(
+ std::clamp<s32>(static_cast<s32>(surfaces_rect.left) + viewport_rect.right * res_scale,
+ surfaces_rect.left, surfaces_rect.right)), // Right
+ static_cast<u32>(std::clamp<s32>(static_cast<s32>(surfaces_rect.bottom) +
+ viewport_rect.bottom * res_scale,
+ surfaces_rect.bottom, surfaces_rect.top))}; // Bottom
+
+ // Bind the framebuffer surfaces
+ BindFramebufferSurfaces(color_surface, depth_surface, has_stencil);
+
+ // Sync the viewport
+ SyncViewport(surfaces_rect, res_scale);
+
+ // Sync the blend state registers
+ SyncBlendState();
+
+ // TODO(bunnei): Sync framebuffer_scale uniform here
+ // TODO(bunnei): Sync scissorbox uniform(s) here
+
+ // Viewport can have negative offsets or larger dimensions than our framebuffer sub-rect. Enable
+ // scissor test to prevent drawing outside of the framebuffer region
+ state.scissor.enabled = true;
+ state.scissor.x = draw_rect.left;
+ state.scissor.y = draw_rect.bottom;
+ state.scissor.width = draw_rect.GetWidth();
+ state.scissor.height = draw_rect.GetHeight();
+ state.Apply();
+
+ // Draw the vertex batch
+ const bool is_indexed = accelerate_draw == AccelDraw::Indexed;
+ const u64 index_buffer_size{regs.index_array.count * regs.index_array.FormatSizeInBytes()};
+ const unsigned vertex_num{is_indexed ? regs.index_array.count : regs.vertex_buffer.count};
+
+ state.draw.vertex_buffer = stream_buffer->GetHandle();
+ state.Apply();
+
+ size_t buffer_size = CalculateVertexArraysSize();
+
+ if (is_indexed) {
+ buffer_size = Common::AlignUp<size_t>(buffer_size, 4) + index_buffer_size;
+ }
+
+ // Uniform space for the 5 shader stages
+ buffer_size = Common::AlignUp<size_t>(buffer_size, 4) +
+ sizeof(GLShader::MaxwellUniformData) * Maxwell::MaxShaderStage;
+
+ u8* buffer_ptr;
+ GLintptr buffer_offset;
+ std::tie(buffer_ptr, buffer_offset) =
+ stream_buffer->Map(static_cast<GLsizeiptr>(buffer_size), 4);
+
+ u8* offseted_buffer;
+ std::tie(offseted_buffer, buffer_offset) = SetupVertexArrays(buffer_ptr, buffer_offset);
+
+ offseted_buffer =
+ reinterpret_cast<u8*>(Common::AlignUp(reinterpret_cast<size_t>(offseted_buffer), 4));
+ buffer_offset = Common::AlignUp<size_t>(buffer_offset, 4);
+
+ // If indexed mode, copy the index buffer
+ GLintptr index_buffer_offset = 0;
+ if (is_indexed) {
+ const auto& memory_manager = Core::System().GetInstance().GPU().memory_manager;
+ const boost::optional<VAddr> index_data_addr{
+ memory_manager->GpuToCpuAddress(regs.index_array.StartAddress())};
+ Memory::ReadBlock(*index_data_addr, offseted_buffer, index_buffer_size);
+
+ index_buffer_offset = buffer_offset;
+ offseted_buffer += index_buffer_size;
+ buffer_offset += index_buffer_size;
+ }
+
+ offseted_buffer =
+ reinterpret_cast<u8*>(Common::AlignUp(reinterpret_cast<size_t>(offseted_buffer), 4));
+ buffer_offset = Common::AlignUp<size_t>(buffer_offset, 4);
+
+ SetupShaders(offseted_buffer, buffer_offset);
+
+ stream_buffer->Unmap();
+
+ shader_program_manager->ApplyTo(state);
+ state.Apply();
+
+ const GLenum primitive_mode{MaxwellToGL::PrimitiveTopology(regs.draw.topology)};
+ if (is_indexed) {
+ const GLint index_min{static_cast<GLint>(regs.index_array.first)};
+ const GLint index_max{static_cast<GLint>(regs.index_array.first + regs.index_array.count)};
+ glDrawRangeElementsBaseVertex(primitive_mode, index_min, index_max, regs.index_array.count,
+ MaxwellToGL::IndexFormat(regs.index_array.format),
+ reinterpret_cast<const void*>(index_buffer_offset),
+ -index_min);
+ } else {
+ glDrawArrays(primitive_mode, 0, regs.vertex_buffer.count);
+ }
+
+ // Disable scissor test
+ state.scissor.enabled = false;
+
+ accelerate_draw = AccelDraw::Disabled;
+
+ // Unbind textures for potential future use as framebuffer attachments
+ for (auto& texture_unit : state.texture_units) {
+ texture_unit.texture_2d = 0;
+ }
+ state.Apply();
+
+ // Mark framebuffer surfaces as dirty
+ MathUtil::Rectangle<u32> draw_rect_unscaled{
+ draw_rect.left / res_scale, draw_rect.top / res_scale, draw_rect.right / res_scale,
+ draw_rect.bottom / res_scale};
+
+ if (color_surface != nullptr && write_color_fb) {
+ auto interval = color_surface->GetSubRectInterval(draw_rect_unscaled);
+ res_cache.InvalidateRegion(boost::icl::first(interval), boost::icl::length(interval),
+ color_surface);
+ }
+ if (depth_surface != nullptr && write_depth_fb) {
+ auto interval = depth_surface->GetSubRectInterval(draw_rect_unscaled);
+ res_cache.InvalidateRegion(boost::icl::first(interval), boost::icl::length(interval),
+ depth_surface);
+ }
+}
+
+void RasterizerOpenGL::NotifyMaxwellRegisterChanged(u32 method) {}
+
+void RasterizerOpenGL::FlushAll() {
+ MICROPROFILE_SCOPE(OpenGL_CacheManagement);
+ res_cache.FlushAll();
+}
+
+void RasterizerOpenGL::FlushRegion(Tegra::GPUVAddr addr, u64 size) {
+ MICROPROFILE_SCOPE(OpenGL_CacheManagement);
+ res_cache.FlushRegion(addr, size);
+}
+
+void RasterizerOpenGL::InvalidateRegion(Tegra::GPUVAddr addr, u64 size) {
+ MICROPROFILE_SCOPE(OpenGL_CacheManagement);
+ res_cache.InvalidateRegion(addr, size, nullptr);
+}
+
+void RasterizerOpenGL::FlushAndInvalidateRegion(Tegra::GPUVAddr addr, u64 size) {
+ MICROPROFILE_SCOPE(OpenGL_CacheManagement);
+ res_cache.FlushRegion(addr, size);
+ res_cache.InvalidateRegion(addr, size, nullptr);
+}
+
+bool RasterizerOpenGL::AccelerateDisplayTransfer(const void* config) {
+ MICROPROFILE_SCOPE(OpenGL_Blits);
+ UNREACHABLE();
+ return true;
+}
+
+bool RasterizerOpenGL::AccelerateTextureCopy(const void* config) {
+ UNREACHABLE();
+ return true;
+}
+
+bool RasterizerOpenGL::AccelerateFill(const void* config) {
+ UNREACHABLE();
+ return true;
+}
+
+bool RasterizerOpenGL::AccelerateDisplay(const Tegra::FramebufferConfig& framebuffer,
+ VAddr framebuffer_addr, u32 pixel_stride,
+ ScreenInfo& screen_info) {
+ if (framebuffer_addr == 0) {
+ return false;
+ }
+ MICROPROFILE_SCOPE(OpenGL_CacheManagement);
+
+ SurfaceParams src_params;
+ src_params.cpu_addr = framebuffer_addr;
+ src_params.addr = res_cache.TryFindFramebufferGpuAddress(framebuffer_addr).get_value_or(0);
+ src_params.width = std::min(framebuffer.width, pixel_stride);
+ src_params.height = framebuffer.height;
+ src_params.stride = pixel_stride;
+ src_params.is_tiled = true;
+ src_params.block_height = Tegra::Texture::TICEntry::DefaultBlockHeight;
+ src_params.pixel_format =
+ SurfaceParams::PixelFormatFromGPUPixelFormat(framebuffer.pixel_format);
+ src_params.component_type =
+ SurfaceParams::ComponentTypeFromGPUPixelFormat(framebuffer.pixel_format);
+ src_params.UpdateParams();
+
+ MathUtil::Rectangle<u32> src_rect;
+ Surface src_surface;
+ std::tie(src_surface, src_rect) =
+ res_cache.GetSurfaceSubRect(src_params, ScaleMatch::Ignore, true);
+
+ if (src_surface == nullptr) {
+ return false;
+ }
+
+ u32 scaled_width = src_surface->GetScaledWidth();
+ u32 scaled_height = src_surface->GetScaledHeight();
+
+ screen_info.display_texcoords = MathUtil::Rectangle<float>(
+ (float)src_rect.bottom / (float)scaled_height, (float)src_rect.left / (float)scaled_width,
+ (float)src_rect.top / (float)scaled_height, (float)src_rect.right / (float)scaled_width);
+
+ screen_info.display_texture = src_surface->texture.handle;
+
+ return true;
+}
+
+void RasterizerOpenGL::SamplerInfo::Create() {
+ sampler.Create();
+ mag_filter = min_filter = Tegra::Texture::TextureFilter::Linear;
+ wrap_u = wrap_v = Tegra::Texture::WrapMode::Wrap;
+ border_color_r = border_color_g = border_color_b = border_color_a = 0;
+
+ // default is GL_LINEAR_MIPMAP_LINEAR
+ glSamplerParameteri(sampler.handle, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ // Other attributes have correct defaults
+}
+
+void RasterizerOpenGL::SamplerInfo::SyncWithConfig(const Tegra::Texture::TSCEntry& config) {
+ 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));
+ }
+ if (min_filter != config.min_filter) {
+ min_filter = config.min_filter;
+ glSamplerParameteri(s, GL_TEXTURE_MIN_FILTER, MaxwellToGL::TextureFilterMode(min_filter));
+ }
+
+ 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_u == Tegra::Texture::WrapMode::Border || wrap_v == Tegra::Texture::WrapMode::Border) {
+ // TODO(Subv): Implement border color
+ ASSERT(false);
+ }
+}
+
+u32 RasterizerOpenGL::SetupConstBuffers(Maxwell::ShaderStage stage, GLuint program,
+ u32 current_bindpoint,
+ const std::vector<GLShader::ConstBufferEntry>& entries) {
+ auto& gpu = Core::System::GetInstance().GPU();
+ auto& maxwell3d = gpu.Get3DEngine();
+
+ ASSERT_MSG(maxwell3d.IsShaderStageEnabled(stage),
+ "Attempted to upload constbuffer of disabled shader stage");
+
+ // Reset all buffer draw state for this stage.
+ for (auto& buffer : state.draw.const_buffers[static_cast<size_t>(stage)]) {
+ buffer.bindpoint = 0;
+ buffer.enabled = false;
+ }
+
+ // Upload only the enabled buffers from the 16 constbuffers of each shader stage
+ auto& shader_stage = maxwell3d.state.shader_stages[static_cast<size_t>(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()];
+ auto& buffer_draw_state =
+ state.draw.const_buffers[static_cast<size_t>(stage)][used_buffer.GetIndex()];
+
+ ASSERT_MSG(buffer.enabled, "Attempted to upload disabled constbuffer");
+ buffer_draw_state.enabled = true;
+ buffer_draw_state.bindpoint = current_bindpoint + bindpoint;
+
+ boost::optional<VAddr> addr = gpu.memory_manager->GpuToCpuAddress(buffer.address);
+
+ std::vector<u8> data;
+ if (used_buffer.IsIndirect()) {
+ // Buffer is accessed indirectly, so upload the entire thing
+ data.resize(buffer.size * sizeof(float));
+ } else {
+ // Buffer is accessed directly, upload just what we use
+ data.resize(used_buffer.GetSize() * sizeof(float));
+ }
+
+ Memory::ReadBlock(*addr, data.data(), data.size());
+
+ glBindBuffer(GL_SHADER_STORAGE_BUFFER, buffer_draw_state.ssbo);
+ glBufferData(GL_SHADER_STORAGE_BUFFER, data.size(), data.data(), GL_DYNAMIC_DRAW);
+ glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
+
+ // Now configure the bindpoint of the buffer inside the shader
+ std::string buffer_name = used_buffer.GetName();
+ GLuint index =
+ glGetProgramResourceIndex(program, GL_SHADER_STORAGE_BLOCK, buffer_name.c_str());
+ if (index != -1)
+ glShaderStorageBlockBinding(program, index, buffer_draw_state.bindpoint);
+ }
+
+ state.Apply();
+
+ return current_bindpoint + entries.size();
+}
+
+u32 RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, GLuint program, u32 current_unit,
+ const std::vector<GLShader::SamplerEntry>& entries) {
+ auto& gpu = Core::System::GetInstance().GPU();
+ auto& maxwell3d = gpu.Get3DEngine();
+
+ ASSERT_MSG(maxwell3d.IsShaderStageEnabled(stage),
+ "Attempted to upload textures of disabled shader stage");
+
+ ASSERT_MSG(current_unit + entries.size() <= std::size(state.texture_units),
+ "Exceeded the number of active textures.");
+
+ for (u32 bindpoint = 0; bindpoint < entries.size(); ++bindpoint) {
+ const auto& entry = entries[bindpoint];
+ u32 current_bindpoint = current_unit + bindpoint;
+
+ // Bind the uniform to the sampler.
+ GLint uniform = glGetUniformLocation(program, entry.GetName().c_str());
+ ASSERT(uniform != -1);
+ glProgramUniform1i(program, uniform, current_bindpoint);
+
+ const auto texture = maxwell3d.GetStageTexture(entry.GetStage(), entry.GetOffset());
+ ASSERT(texture.enabled);
+
+ texture_samplers[current_bindpoint].SyncWithConfig(texture.tsc);
+ Surface surface = res_cache.GetTextureSurface(texture);
+ if (surface != nullptr) {
+ state.texture_units[current_bindpoint].texture_2d = surface->texture.handle;
+ state.texture_units[current_bindpoint].swizzle.r =
+ MaxwellToGL::SwizzleSource(texture.tic.x_source);
+ state.texture_units[current_bindpoint].swizzle.g =
+ MaxwellToGL::SwizzleSource(texture.tic.y_source);
+ state.texture_units[current_bindpoint].swizzle.b =
+ MaxwellToGL::SwizzleSource(texture.tic.z_source);
+ state.texture_units[current_bindpoint].swizzle.a =
+ MaxwellToGL::SwizzleSource(texture.tic.w_source);
+ } else {
+ // Can occur when texture addr is null or its memory is unmapped/invalid
+ state.texture_units[current_bindpoint].texture_2d = 0;
+ }
+ }
+
+ state.Apply();
+
+ return current_unit + entries.size();
+}
+
+void RasterizerOpenGL::BindFramebufferSurfaces(const Surface& color_surface,
+ const Surface& depth_surface, bool has_stencil) {
+ state.draw.draw_framebuffer = framebuffer.handle;
+ state.Apply();
+
+ glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ color_surface != nullptr ? color_surface->texture.handle : 0, 0);
+ if (depth_surface != nullptr) {
+ if (has_stencil) {
+ // attach both depth and stencil
+ glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D,
+ depth_surface->texture.handle, 0);
+ } else {
+ // attach depth
+ glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D,
+ depth_surface->texture.handle, 0);
+ // clear stencil attachment
+ glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
+ }
+ } else {
+ // clear both depth and stencil attachment
+ glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0,
+ 0);
+ }
+}
+
+void RasterizerOpenGL::SyncViewport(const MathUtil::Rectangle<u32>& surfaces_rect, u16 res_scale) {
+ const auto& regs = Core::System().GetInstance().GPU().Maxwell3D().regs;
+ const MathUtil::Rectangle<s32> viewport_rect{regs.viewport_transform[0].GetRect()};
+
+ state.viewport.x = static_cast<GLint>(surfaces_rect.left) + viewport_rect.left * res_scale;
+ state.viewport.y = static_cast<GLint>(surfaces_rect.bottom) + viewport_rect.bottom * res_scale;
+ state.viewport.width = static_cast<GLsizei>(viewport_rect.GetWidth() * res_scale);
+ state.viewport.height = static_cast<GLsizei>(viewport_rect.GetHeight() * res_scale);
+}
+
+void RasterizerOpenGL::SyncClipEnabled() {
+ UNREACHABLE();
+}
+
+void RasterizerOpenGL::SyncClipCoef() {
+ UNREACHABLE();
+}
+
+void RasterizerOpenGL::SyncCullMode() {
+ UNREACHABLE();
+}
+
+void RasterizerOpenGL::SyncDepthScale() {
+ UNREACHABLE();
+}
+
+void RasterizerOpenGL::SyncDepthOffset() {
+ UNREACHABLE();
+}
+
+void RasterizerOpenGL::SyncBlendState() {
+ const auto& regs = Core::System().GetInstance().GPU().Maxwell3D().regs;
+
+ // TODO(Subv): Support more than just render target 0.
+ state.blend.enabled = regs.blend.enable[0] != 0;
+
+ if (!state.blend.enabled)
+ return;
+
+ ASSERT_MSG(regs.independent_blend_enable == 1, "Only independent blending is implemented");
+ ASSERT_MSG(!regs.independent_blend[0].separate_alpha, "Unimplemented");
+ state.blend.rgb_equation = MaxwellToGL::BlendEquation(regs.independent_blend[0].equation_rgb);
+ state.blend.src_rgb_func = MaxwellToGL::BlendFunc(regs.independent_blend[0].factor_source_rgb);
+ state.blend.dst_rgb_func = MaxwellToGL::BlendFunc(regs.independent_blend[0].factor_dest_rgb);
+ state.blend.a_equation = MaxwellToGL::BlendEquation(regs.independent_blend[0].equation_a);
+ state.blend.src_a_func = MaxwellToGL::BlendFunc(regs.independent_blend[0].factor_source_a);
+ state.blend.dst_a_func = MaxwellToGL::BlendFunc(regs.independent_blend[0].factor_dest_a);
+}
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
new file mode 100644
index 000000000..b7c8cf843
--- /dev/null
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -0,0 +1,160 @@
+// Copyright 2015 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <cstddef>
+#include <memory>
+#include <vector>
+#include <glad/glad.h>
+#include "common/common_types.h"
+#include "video_core/engines/maxwell_3d.h"
+#include "video_core/memory_manager.h"
+#include "video_core/rasterizer_interface.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_shader_gen.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"
+
+struct ScreenInfo;
+
+class RasterizerOpenGL : public VideoCore::RasterizerInterface {
+public:
+ RasterizerOpenGL();
+ ~RasterizerOpenGL() override;
+
+ void DrawArrays() override;
+ void NotifyMaxwellRegisterChanged(u32 method) override;
+ void FlushAll() override;
+ void FlushRegion(Tegra::GPUVAddr addr, u64 size) override;
+ void InvalidateRegion(Tegra::GPUVAddr addr, u64 size) override;
+ void FlushAndInvalidateRegion(Tegra::GPUVAddr addr, u64 size) override;
+ bool AccelerateDisplayTransfer(const void* config) override;
+ bool AccelerateTextureCopy(const void* config) override;
+ bool AccelerateFill(const void* config) override;
+ bool AccelerateDisplay(const Tegra::FramebufferConfig& framebuffer, VAddr framebuffer_addr,
+ u32 pixel_stride, ScreenInfo& screen_info) override;
+ bool AccelerateDrawBatch(bool is_indexed) override;
+
+ /// OpenGL shader generated for a given Maxwell register state
+ struct MaxwellShader {
+ /// OpenGL shader resource
+ OGLProgram shader;
+ };
+
+ struct VertexShader {
+ OGLShader shader;
+ };
+
+ struct FragmentShader {
+ OGLShader shader;
+ };
+
+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& config);
+
+ private:
+ Tegra::Texture::TextureFilter mag_filter;
+ Tegra::Texture::TextureFilter min_filter;
+ Tegra::Texture::WrapMode wrap_u;
+ Tegra::Texture::WrapMode wrap_v;
+ u32 border_color_r;
+ u32 border_color_g;
+ u32 border_color_b;
+ u32 border_color_a;
+ };
+
+ /// Binds the framebuffer color and depth surface
+ void BindFramebufferSurfaces(const Surface& color_surface, const Surface& depth_surface,
+ bool has_stencil);
+
+ /*
+ * Configures the current constbuffers to use for the draw command.
+ * @param stage The shader stage to configure buffers for.
+ * @param program The OpenGL program object that contains the specified stage.
+ * @param current_bindpoint The offset at which to start counting new buffer bindpoints.
+ * @param entries Vector describing the buffers that are actually used in the guest shader.
+ * @returns The next available bindpoint for use in the next shader stage.
+ */
+ u32 SetupConstBuffers(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, GLuint program,
+ u32 current_bindpoint,
+ const std::vector<GLShader::ConstBufferEntry>& entries);
+
+ /*
+ * Configures the current textures to use for the draw command.
+ * @param stage The shader stage to configure textures for.
+ * @param program The OpenGL program object that contains the specified stage.
+ * @param current_unit The offset at which to start counting unused texture units.
+ * @param entries Vector describing the textures that are actually used in the guest shader.
+ * @returns The next available bindpoint for use in the next shader stage.
+ */
+ u32 SetupTextures(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, GLuint program,
+ u32 current_unit, const std::vector<GLShader::SamplerEntry>& entries);
+
+ /// Syncs the viewport to match the guest state
+ void SyncViewport(const MathUtil::Rectangle<u32>& surfaces_rect, u16 res_scale);
+
+ /// Syncs the clip enabled status to match the guest state
+ void SyncClipEnabled();
+
+ /// Syncs the clip coefficients to match the guest state
+ void SyncClipCoef();
+
+ /// Syncs the cull mode to match the guest state
+ void SyncCullMode();
+
+ /// Syncs the depth scale to match the guest state
+ void SyncDepthScale();
+
+ /// Syncs the depth offset to match the guest state
+ void SyncDepthOffset();
+
+ /// Syncs the blend state to match the guest state
+ void SyncBlendState();
+
+ bool has_ARB_buffer_storage;
+ bool has_ARB_direct_state_access;
+ bool has_ARB_separate_shader_objects;
+ bool has_ARB_vertex_attrib_binding;
+
+ OpenGLState state;
+
+ RasterizerCacheOpenGL res_cache;
+
+ std::unique_ptr<GLShader::ProgramManager> shader_program_manager;
+ OGLVertexArray sw_vao;
+ OGLVertexArray hw_vao;
+
+ std::array<SamplerInfo, GLShader::NumTextureSamplers> texture_samplers;
+ std::array<std::array<OGLBuffer, Tegra::Engines::Maxwell3D::Regs::MaxConstBuffers>,
+ Tegra::Engines::Maxwell3D::Regs::MaxShaderStage>
+ ssbos;
+
+ static constexpr size_t STREAM_BUFFER_SIZE = 128 * 1024 * 1024;
+ std::unique_ptr<OGLStreamBuffer> stream_buffer;
+ OGLBuffer uniform_buffer;
+ OGLFramebuffer framebuffer;
+
+ size_t CalculateVertexArraysSize() const;
+
+ std::pair<u8*, GLintptr> SetupVertexArrays(u8* array_ptr, GLintptr buffer_offset);
+
+ std::array<OGLBuffer, Tegra::Engines::Maxwell3D::Regs::MaxShaderStage> uniform_buffers;
+
+ void SetupShaders(u8* buffer_ptr, GLintptr buffer_offset);
+
+ enum class AccelDraw { Disabled, Arrays, Indexed };
+ AccelDraw accelerate_draw;
+};
diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
new file mode 100644
index 000000000..e61960cc0
--- /dev/null
+++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
@@ -0,0 +1,1418 @@
+// Copyright 2015 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <atomic>
+#include <cstring>
+#include <iterator>
+#include <memory>
+#include <utility>
+#include <vector>
+#include <boost/optional.hpp>
+#include <boost/range/iterator_range.hpp>
+#include <glad/glad.h>
+#include "common/alignment.h"
+#include "common/bit_field.h"
+#include "common/color.h"
+#include "common/logging/log.h"
+#include "common/math_util.h"
+#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/hle/kernel/vm_manager.h"
+#include "core/memory.h"
+#include "core/settings.h"
+#include "video_core/engines/maxwell_3d.h"
+#include "video_core/renderer_opengl/gl_rasterizer_cache.h"
+#include "video_core/renderer_opengl/gl_state.h"
+#include "video_core/textures/decoders.h"
+#include "video_core/utils.h"
+#include "video_core/video_core.h"
+
+using SurfaceType = SurfaceParams::SurfaceType;
+using PixelFormat = SurfaceParams::PixelFormat;
+using ComponentType = SurfaceParams::ComponentType;
+
+struct FormatTuple {
+ GLint internal_format;
+ GLenum format;
+ GLenum type;
+ bool compressed;
+};
+
+static constexpr std::array<FormatTuple, SurfaceParams::MaxPixelFormat> tex_format_tuples = {{
+ {GL_RGBA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, false}, // ABGR8
+ {GL_RGB, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, false}, // B5G6R5
+ {GL_RGB10_A2, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV, false}, // A2B10G10R10
+ {GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, false}, // A1B5G5R5
+ {GL_R8, GL_RED, GL_UNSIGNED_BYTE, false}, // R8
+ {GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT, false}, // RGBA16F
+ {GL_R11F_G11F_B10F, GL_RGB, GL_UNSIGNED_INT_10F_11F_11F_REV, false}, // R11FG11FB10F
+ {GL_COMPRESSED_RGB_S3TC_DXT1_EXT, GL_RGB, GL_UNSIGNED_INT_8_8_8_8, true}, // DXT1
+ {GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, true}, // DXT23
+ {GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, true}, // DXT45
+ {GL_COMPRESSED_RED_RGTC1, GL_RED, GL_UNSIGNED_INT_8_8_8_8, true}, // DXN1
+}};
+
+static const FormatTuple& GetFormatTuple(PixelFormat pixel_format, ComponentType component_type) {
+ const SurfaceType type = SurfaceParams::GetFormatType(pixel_format);
+ if (type == SurfaceType::ColorTexture) {
+ ASSERT(static_cast<size_t>(pixel_format) < tex_format_tuples.size());
+ // For now only UNORM components are supported, or either R11FG11FB10F or RGBA16F which are
+ // type FLOAT
+ ASSERT(component_type == ComponentType::UNorm || pixel_format == PixelFormat::RGBA16F ||
+ pixel_format == PixelFormat::R11FG11FB10F);
+ return tex_format_tuples[static_cast<unsigned int>(pixel_format)];
+ } else if (type == SurfaceType::Depth || type == SurfaceType::DepthStencil) {
+ // TODO(Subv): Implement depth formats
+ ASSERT_MSG(false, "Unimplemented");
+ }
+
+ UNREACHABLE();
+ return {};
+}
+
+template <typename Map, typename Interval>
+constexpr auto RangeFromInterval(Map& map, const Interval& interval) {
+ return boost::make_iterator_range(map.equal_range(interval));
+}
+
+static u16 GetResolutionScaleFactor() {
+ return static_cast<u16>(!Settings::values.resolution_factor
+ ? VideoCore::g_emu_window->GetFramebufferLayout().GetScalingRatio()
+ : Settings::values.resolution_factor);
+}
+
+template <bool morton_to_gl, PixelFormat format>
+void MortonCopy(u32 stride, u32 block_height, u32 height, u8* gl_buffer, Tegra::GPUVAddr base,
+ Tegra::GPUVAddr start, Tegra::GPUVAddr end) {
+ constexpr u32 bytes_per_pixel = SurfaceParams::GetFormatBpp(format) / CHAR_BIT;
+ constexpr u32 gl_bytes_per_pixel = CachedSurface::GetGLBytesPerPixel(format);
+ const auto& gpu = Core::System::GetInstance().GPU();
+
+ if (morton_to_gl) {
+ auto data = Tegra::Texture::UnswizzleTexture(
+ *gpu.memory_manager->GpuToCpuAddress(base),
+ SurfaceParams::TextureFormatFromPixelFormat(format), stride, height, block_height);
+ std::memcpy(gl_buffer, data.data(), data.size());
+ } else {
+ // TODO(bunnei): Assumes the default rendering GOB size of 16 (128 lines). We should check
+ // the configuration for this and perform more generic un/swizzle
+ NGLOG_WARNING(Render_OpenGL, "need to use correct swizzle/GOB parameters!");
+ VideoCore::MortonCopyPixels128(
+ stride, height, bytes_per_pixel, gl_bytes_per_pixel,
+ Memory::GetPointer(*gpu.memory_manager->GpuToCpuAddress(base)), gl_buffer,
+ morton_to_gl);
+ }
+}
+
+static constexpr std::array<void (*)(u32, u32, u32, u8*, Tegra::GPUVAddr, Tegra::GPUVAddr,
+ Tegra::GPUVAddr),
+ SurfaceParams::MaxPixelFormat>
+ morton_to_gl_fns = {
+ MortonCopy<true, PixelFormat::ABGR8>, MortonCopy<true, PixelFormat::B5G6R5>,
+ MortonCopy<true, PixelFormat::A2B10G10R10>, MortonCopy<true, PixelFormat::A1B5G5R5>,
+ MortonCopy<true, PixelFormat::R8>, MortonCopy<true, PixelFormat::RGBA16F>,
+ MortonCopy<true, PixelFormat::R11FG11FB10F>, MortonCopy<true, PixelFormat::DXT1>,
+ MortonCopy<true, PixelFormat::DXT23>, MortonCopy<true, PixelFormat::DXT45>,
+ MortonCopy<true, PixelFormat::DXN1>,
+};
+
+static constexpr std::array<void (*)(u32, u32, u32, u8*, Tegra::GPUVAddr, Tegra::GPUVAddr,
+ Tegra::GPUVAddr),
+ SurfaceParams::MaxPixelFormat>
+ gl_to_morton_fns = {
+ MortonCopy<false, PixelFormat::ABGR8>,
+ MortonCopy<false, PixelFormat::B5G6R5>,
+ MortonCopy<false, PixelFormat::A2B10G10R10>,
+ MortonCopy<false, PixelFormat::A1B5G5R5>,
+ MortonCopy<false, PixelFormat::R8>,
+ MortonCopy<false, PixelFormat::RGBA16F>,
+ MortonCopy<false, PixelFormat::R11FG11FB10F>,
+ // TODO(Subv): Swizzling the DXT1/DXT23/DXT45/DXN1 formats is not yet supported
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+};
+
+// Allocate an uninitialized texture of appropriate size and format for the surface
+static void AllocateSurfaceTexture(GLuint texture, const FormatTuple& format_tuple, u32 width,
+ u32 height) {
+ OpenGLState cur_state = OpenGLState::GetCurState();
+
+ // Keep track of previous texture bindings
+ GLuint old_tex = cur_state.texture_units[0].texture_2d;
+ cur_state.texture_units[0].texture_2d = texture;
+ cur_state.Apply();
+ glActiveTexture(GL_TEXTURE0);
+
+ if (!format_tuple.compressed) {
+ // Only pre-create the texture for non-compressed textures.
+ glTexImage2D(GL_TEXTURE_2D, 0, format_tuple.internal_format, width, height, 0,
+ format_tuple.format, format_tuple.type, nullptr);
+ }
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ // Restore previous texture bindings
+ cur_state.texture_units[0].texture_2d = old_tex;
+ cur_state.Apply();
+}
+
+static bool BlitTextures(GLuint src_tex, const MathUtil::Rectangle<u32>& src_rect, GLuint dst_tex,
+ const MathUtil::Rectangle<u32>& dst_rect, SurfaceType type,
+ GLuint read_fb_handle, GLuint draw_fb_handle) {
+
+ glCopyImageSubData(src_tex, GL_TEXTURE_2D, 0, src_rect.left, src_rect.bottom, 0, dst_tex,
+ GL_TEXTURE_2D, 0, dst_rect.left, dst_rect.bottom, 0, src_rect.GetWidth(),
+ src_rect.GetHeight(), 0);
+ return true;
+}
+
+static bool FillSurface(const Surface& surface, const u8* fill_data,
+ const MathUtil::Rectangle<u32>& fill_rect, GLuint draw_fb_handle) {
+ UNREACHABLE();
+ return {};
+}
+
+SurfaceParams SurfaceParams::FromInterval(SurfaceInterval interval) const {
+ SurfaceParams params = *this;
+ const u32 tiled_size = is_tiled ? 8 : 1;
+ const u64 stride_tiled_bytes = BytesInPixels(stride * tiled_size);
+ Tegra::GPUVAddr aligned_start =
+ addr + Common::AlignDown(boost::icl::first(interval) - addr, stride_tiled_bytes);
+ Tegra::GPUVAddr aligned_end =
+ addr + Common::AlignUp(boost::icl::last_next(interval) - addr, stride_tiled_bytes);
+
+ if (aligned_end - aligned_start > stride_tiled_bytes) {
+ params.addr = aligned_start;
+ params.height = static_cast<u32>((aligned_end - aligned_start) / BytesInPixels(stride));
+ } else {
+ // 1 row
+ ASSERT(aligned_end - aligned_start == stride_tiled_bytes);
+ const u64 tiled_alignment = BytesInPixels(is_tiled ? 8 * 8 : 1);
+ aligned_start =
+ addr + Common::AlignDown(boost::icl::first(interval) - addr, tiled_alignment);
+ aligned_end =
+ addr + Common::AlignUp(boost::icl::last_next(interval) - addr, tiled_alignment);
+ params.addr = aligned_start;
+ params.width = static_cast<u32>(PixelsInBytes(aligned_end - aligned_start) / tiled_size);
+ params.stride = params.width;
+ params.height = tiled_size;
+ }
+ params.UpdateParams();
+
+ return params;
+}
+
+SurfaceInterval SurfaceParams::GetSubRectInterval(MathUtil::Rectangle<u32> unscaled_rect) const {
+ if (unscaled_rect.GetHeight() == 0 || unscaled_rect.GetWidth() == 0) {
+ return {};
+ }
+
+ if (is_tiled) {
+ unscaled_rect.left = Common::AlignDown(unscaled_rect.left, 8) * 8;
+ unscaled_rect.bottom = Common::AlignDown(unscaled_rect.bottom, 8) / 8;
+ unscaled_rect.right = Common::AlignUp(unscaled_rect.right, 8) * 8;
+ unscaled_rect.top = Common::AlignUp(unscaled_rect.top, 8) / 8;
+ }
+
+ const u32 stride_tiled = !is_tiled ? stride : stride * 8;
+
+ const u32 pixel_offset =
+ stride_tiled * (!is_tiled ? unscaled_rect.bottom : (height / 8) - unscaled_rect.top) +
+ unscaled_rect.left;
+
+ const u32 pixels = (unscaled_rect.GetHeight() - 1) * stride_tiled + unscaled_rect.GetWidth();
+
+ return {addr + BytesInPixels(pixel_offset), addr + BytesInPixels(pixel_offset + pixels)};
+}
+
+MathUtil::Rectangle<u32> SurfaceParams::GetSubRect(const SurfaceParams& sub_surface) const {
+ const u32 begin_pixel_index = static_cast<u32>(PixelsInBytes(sub_surface.addr - addr));
+
+ if (is_tiled) {
+ const int x0 = (begin_pixel_index % (stride * 8)) / 8;
+ const int y0 = (begin_pixel_index / (stride * 8)) * 8;
+ // Top to bottom
+ return MathUtil::Rectangle<u32>(x0, height - y0, x0 + sub_surface.width,
+ height - (y0 + sub_surface.height));
+ }
+
+ const int x0 = begin_pixel_index % stride;
+ const int y0 = begin_pixel_index / stride;
+ // Bottom to top
+ return MathUtil::Rectangle<u32>(x0, y0 + sub_surface.height, x0 + sub_surface.width, y0);
+}
+
+MathUtil::Rectangle<u32> SurfaceParams::GetScaledSubRect(const SurfaceParams& sub_surface) const {
+ auto rect = GetSubRect(sub_surface);
+ rect.left = rect.left * res_scale;
+ rect.right = rect.right * res_scale;
+ rect.top = rect.top * res_scale;
+ rect.bottom = rect.bottom * res_scale;
+ return rect;
+}
+
+bool SurfaceParams::ExactMatch(const SurfaceParams& other_surface) const {
+ return std::tie(other_surface.addr, other_surface.width, other_surface.height,
+ other_surface.stride, other_surface.block_height, other_surface.pixel_format,
+ other_surface.component_type,
+ other_surface.is_tiled) == std::tie(addr, width, height, stride, block_height,
+ pixel_format, component_type, is_tiled) &&
+ pixel_format != PixelFormat::Invalid;
+}
+
+bool SurfaceParams::CanSubRect(const SurfaceParams& sub_surface) const {
+ return sub_surface.addr >= addr && sub_surface.end <= end &&
+ sub_surface.pixel_format == pixel_format && pixel_format != PixelFormat::Invalid &&
+ sub_surface.is_tiled == is_tiled && sub_surface.block_height == block_height &&
+ sub_surface.component_type == component_type &&
+ (sub_surface.addr - addr) % BytesInPixels(is_tiled ? 64 : 1) == 0 &&
+ (sub_surface.stride == stride || sub_surface.height <= (is_tiled ? 8u : 1u)) &&
+ GetSubRect(sub_surface).left + sub_surface.width <= stride;
+}
+
+bool SurfaceParams::CanExpand(const SurfaceParams& expanded_surface) const {
+ return pixel_format != PixelFormat::Invalid && pixel_format == expanded_surface.pixel_format &&
+ addr <= expanded_surface.end && expanded_surface.addr <= end &&
+ is_tiled == expanded_surface.is_tiled && block_height == expanded_surface.block_height &&
+ component_type == expanded_surface.component_type && stride == expanded_surface.stride &&
+ (std::max(expanded_surface.addr, addr) - std::min(expanded_surface.addr, addr)) %
+ BytesInPixels(stride * (is_tiled ? 8 : 1)) ==
+ 0;
+}
+
+bool SurfaceParams::CanTexCopy(const SurfaceParams& texcopy_params) const {
+ if (pixel_format == PixelFormat::Invalid || addr > texcopy_params.addr ||
+ end < texcopy_params.end) {
+ return false;
+ }
+ if (texcopy_params.block_height != block_height ||
+ texcopy_params.component_type != component_type)
+ return false;
+
+ if (texcopy_params.width != texcopy_params.stride) {
+ const u32 tile_stride = static_cast<u32>(BytesInPixels(stride * (is_tiled ? 8 : 1)));
+ return (texcopy_params.addr - addr) % BytesInPixels(is_tiled ? 64 : 1) == 0 &&
+ texcopy_params.width % BytesInPixels(is_tiled ? 64 : 1) == 0 &&
+ (texcopy_params.height == 1 || texcopy_params.stride == tile_stride) &&
+ ((texcopy_params.addr - addr) % tile_stride) + texcopy_params.width <= tile_stride;
+ }
+ return FromInterval(texcopy_params.GetInterval()).GetInterval() == texcopy_params.GetInterval();
+}
+
+VAddr SurfaceParams::GetCpuAddr() const {
+ // When this function is used, only cpu_addr or (GPU) addr should be set, not both
+ ASSERT(!(cpu_addr && addr));
+ const auto& gpu = Core::System::GetInstance().GPU();
+ return cpu_addr.get_value_or(*gpu.memory_manager->GpuToCpuAddress(addr));
+}
+
+bool CachedSurface::CanFill(const SurfaceParams& dest_surface,
+ SurfaceInterval fill_interval) const {
+ if (type == SurfaceType::Fill && IsRegionValid(fill_interval) &&
+ boost::icl::first(fill_interval) >= addr &&
+ boost::icl::last_next(fill_interval) <= end && // dest_surface is within our fill range
+ dest_surface.FromInterval(fill_interval).GetInterval() ==
+ fill_interval) { // make sure interval is a rectangle in dest surface
+ if (fill_size * CHAR_BIT != dest_surface.GetFormatBpp()) {
+ // Check if bits repeat for our fill_size
+ const u32 dest_bytes_per_pixel = std::max(dest_surface.GetFormatBpp() / CHAR_BIT, 1u);
+ std::vector<u8> fill_test(fill_size * dest_bytes_per_pixel);
+
+ for (u32 i = 0; i < dest_bytes_per_pixel; ++i)
+ std::memcpy(&fill_test[i * fill_size], &fill_data[0], fill_size);
+
+ for (u32 i = 0; i < fill_size; ++i)
+ if (std::memcmp(&fill_test[dest_bytes_per_pixel * i], &fill_test[0],
+ dest_bytes_per_pixel) != 0)
+ return false;
+
+ if (dest_surface.GetFormatBpp() == 4 && (fill_test[0] & 0xF) != (fill_test[0] >> 4))
+ return false;
+ }
+ return true;
+ }
+ return false;
+}
+
+bool CachedSurface::CanCopy(const SurfaceParams& dest_surface,
+ SurfaceInterval copy_interval) const {
+ SurfaceParams subrect_params = dest_surface.FromInterval(copy_interval);
+ ASSERT(subrect_params.GetInterval() == copy_interval);
+ if (CanSubRect(subrect_params))
+ return true;
+
+ if (CanFill(dest_surface, copy_interval))
+ return true;
+
+ return false;
+}
+
+SurfaceInterval SurfaceParams::GetCopyableInterval(const Surface& src_surface) const {
+ SurfaceInterval result{};
+ const auto valid_regions =
+ SurfaceRegions(GetInterval() & src_surface->GetInterval()) - src_surface->invalid_regions;
+ for (auto& valid_interval : valid_regions) {
+ const SurfaceInterval aligned_interval{
+ addr + Common::AlignUp(boost::icl::first(valid_interval) - addr,
+ BytesInPixels(is_tiled ? 8 * 8 : 1)),
+ addr + Common::AlignDown(boost::icl::last_next(valid_interval) - addr,
+ BytesInPixels(is_tiled ? 8 * 8 : 1))};
+
+ if (BytesInPixels(is_tiled ? 8 * 8 : 1) > boost::icl::length(valid_interval) ||
+ boost::icl::length(aligned_interval) == 0) {
+ continue;
+ }
+
+ // Get the rectangle within aligned_interval
+ const u32 stride_bytes = static_cast<u32>(BytesInPixels(stride)) * (is_tiled ? 8 : 1);
+ SurfaceInterval rect_interval{
+ addr + Common::AlignUp(boost::icl::first(aligned_interval) - addr, stride_bytes),
+ addr + Common::AlignDown(boost::icl::last_next(aligned_interval) - addr, stride_bytes),
+ };
+ if (boost::icl::first(rect_interval) > boost::icl::last_next(rect_interval)) {
+ // 1 row
+ rect_interval = aligned_interval;
+ } else if (boost::icl::length(rect_interval) == 0) {
+ // 2 rows that do not make a rectangle, return the larger one
+ const SurfaceInterval row1{boost::icl::first(aligned_interval),
+ boost::icl::first(rect_interval)};
+ const SurfaceInterval row2{boost::icl::first(rect_interval),
+ boost::icl::last_next(aligned_interval)};
+ rect_interval = (boost::icl::length(row1) > boost::icl::length(row2)) ? row1 : row2;
+ }
+
+ if (boost::icl::length(rect_interval) > boost::icl::length(result)) {
+ result = rect_interval;
+ }
+ }
+ return result;
+}
+
+void RasterizerCacheOpenGL::CopySurface(const Surface& src_surface, const Surface& dst_surface,
+ SurfaceInterval copy_interval) {
+ SurfaceParams subrect_params = dst_surface->FromInterval(copy_interval);
+ ASSERT(subrect_params.GetInterval() == copy_interval);
+
+ ASSERT(src_surface != dst_surface);
+
+ // This is only called when CanCopy is true, no need to run checks here
+ if (src_surface->type == SurfaceType::Fill) {
+ // FillSurface needs a 4 bytes buffer
+ const u64 fill_offset =
+ (boost::icl::first(copy_interval) - src_surface->addr) % src_surface->fill_size;
+ std::array<u8, 4> fill_buffer;
+
+ u64 fill_buff_pos = fill_offset;
+ for (int i : {0, 1, 2, 3})
+ fill_buffer[i] = src_surface->fill_data[fill_buff_pos++ % src_surface->fill_size];
+
+ FillSurface(dst_surface, &fill_buffer[0], dst_surface->GetScaledSubRect(subrect_params),
+ draw_framebuffer.handle);
+ return;
+ }
+ if (src_surface->CanSubRect(subrect_params)) {
+ BlitTextures(src_surface->texture.handle, src_surface->GetScaledSubRect(subrect_params),
+ dst_surface->texture.handle, dst_surface->GetScaledSubRect(subrect_params),
+ src_surface->type, read_framebuffer.handle, draw_framebuffer.handle);
+ return;
+ }
+ UNREACHABLE();
+}
+
+MICROPROFILE_DEFINE(OpenGL_SurfaceLoad, "OpenGL", "Surface Load", MP_RGB(128, 64, 192));
+void CachedSurface::LoadGLBuffer(Tegra::GPUVAddr load_start, Tegra::GPUVAddr load_end) {
+ ASSERT(type != SurfaceType::Fill);
+
+ u8* const texture_src_data = Memory::GetPointer(GetCpuAddr());
+ if (texture_src_data == nullptr)
+ return;
+
+ if (gl_buffer == nullptr) {
+ gl_buffer_size = GetActualWidth() * GetActualHeight() * GetGLBytesPerPixel(pixel_format);
+ gl_buffer.reset(new u8[gl_buffer_size]);
+ }
+
+ MICROPROFILE_SCOPE(OpenGL_SurfaceLoad);
+
+ ASSERT(load_start >= addr && load_end <= end);
+ const u64 start_offset = load_start - addr;
+
+ if (!is_tiled) {
+ const u32 bytes_per_pixel{GetFormatBpp() >> 3};
+
+ std::memcpy(&gl_buffer[start_offset], texture_src_data + start_offset,
+ bytes_per_pixel * width * height);
+ } else {
+ morton_to_gl_fns[static_cast<size_t>(pixel_format)](GetActualWidth(), block_height,
+ GetActualHeight(), &gl_buffer[0], addr,
+ load_start, load_end);
+ }
+}
+
+MICROPROFILE_DEFINE(OpenGL_SurfaceFlush, "OpenGL", "Surface Flush", MP_RGB(128, 192, 64));
+void CachedSurface::FlushGLBuffer(Tegra::GPUVAddr flush_start, Tegra::GPUVAddr flush_end) {
+ u8* const dst_buffer = Memory::GetPointer(GetCpuAddr());
+ if (dst_buffer == nullptr)
+ return;
+
+ ASSERT(gl_buffer_size == width * height * GetGLBytesPerPixel(pixel_format));
+
+ // TODO: Should probably be done in ::Memory:: and check for other regions too
+ // same as loadglbuffer()
+ if (flush_start < Memory::VRAM_VADDR_END && flush_end > Memory::VRAM_VADDR_END)
+ flush_end = Memory::VRAM_VADDR_END;
+
+ if (flush_start < Memory::VRAM_VADDR && flush_end > Memory::VRAM_VADDR)
+ flush_start = Memory::VRAM_VADDR;
+
+ MICROPROFILE_SCOPE(OpenGL_SurfaceFlush);
+
+ ASSERT(flush_start >= addr && flush_end <= end);
+ const u64 start_offset = flush_start - addr;
+ const u64 end_offset = flush_end - addr;
+
+ if (type == SurfaceType::Fill) {
+ const u64 coarse_start_offset = start_offset - (start_offset % fill_size);
+ const u64 backup_bytes = start_offset % fill_size;
+ std::array<u8, 4> backup_data;
+ if (backup_bytes)
+ std::memcpy(&backup_data[0], &dst_buffer[coarse_start_offset], backup_bytes);
+
+ for (u64 offset = coarse_start_offset; offset < end_offset; offset += fill_size) {
+ std::memcpy(&dst_buffer[offset], &fill_data[0],
+ std::min(fill_size, end_offset - offset));
+ }
+
+ if (backup_bytes)
+ std::memcpy(&dst_buffer[coarse_start_offset], &backup_data[0], backup_bytes);
+ } else if (!is_tiled) {
+ std::memcpy(dst_buffer + start_offset, &gl_buffer[start_offset], flush_end - flush_start);
+ } else {
+ gl_to_morton_fns[static_cast<size_t>(pixel_format)](
+ stride, block_height, height, &gl_buffer[0], addr, flush_start, flush_end);
+ }
+}
+
+MICROPROFILE_DEFINE(OpenGL_TextureUL, "OpenGL", "Texture Upload", MP_RGB(128, 64, 192));
+void CachedSurface::UploadGLTexture(const MathUtil::Rectangle<u32>& rect, GLuint read_fb_handle,
+ GLuint draw_fb_handle) {
+ if (type == SurfaceType::Fill)
+ return;
+
+ MICROPROFILE_SCOPE(OpenGL_TextureUL);
+
+ ASSERT(gl_buffer_size ==
+ GetActualWidth() * GetActualHeight() * GetGLBytesPerPixel(pixel_format));
+
+ // Load data from memory to the surface
+ GLint x0 = static_cast<GLint>(rect.left);
+ GLint y0 = static_cast<GLint>(rect.bottom);
+ size_t buffer_offset = (y0 * stride + x0) * GetGLBytesPerPixel(pixel_format);
+
+ const FormatTuple& tuple = GetFormatTuple(pixel_format, component_type);
+ GLuint target_tex = texture.handle;
+
+ // If not 1x scale, create 1x texture that we will blit from to replace texture subrect in
+ // surface
+ OGLTexture unscaled_tex;
+ if (res_scale != 1) {
+ x0 = 0;
+ y0 = 0;
+
+ unscaled_tex.Create();
+ AllocateSurfaceTexture(unscaled_tex.handle, tuple, rect.GetWidth(), rect.GetHeight());
+ target_tex = unscaled_tex.handle;
+ }
+
+ OpenGLState cur_state = OpenGLState::GetCurState();
+
+ GLuint old_tex = cur_state.texture_units[0].texture_2d;
+ cur_state.texture_units[0].texture_2d = target_tex;
+ cur_state.Apply();
+
+ // Ensure no bad interactions with GL_UNPACK_ALIGNMENT
+ ASSERT(stride * GetGLBytesPerPixel(pixel_format) % 4 == 0);
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(stride));
+
+ glActiveTexture(GL_TEXTURE0);
+ if (tuple.compressed) {
+ glCompressedTexImage2D(GL_TEXTURE_2D, 0, tuple.internal_format,
+ static_cast<GLsizei>(rect.GetWidth() * GetCompresssionFactor()),
+ static_cast<GLsizei>(rect.GetHeight() * GetCompresssionFactor()), 0,
+ size, &gl_buffer[buffer_offset]);
+ } else {
+ glTexSubImage2D(GL_TEXTURE_2D, 0, x0, y0, static_cast<GLsizei>(rect.GetWidth()),
+ static_cast<GLsizei>(rect.GetHeight()), tuple.format, tuple.type,
+ &gl_buffer[buffer_offset]);
+ }
+
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
+
+ cur_state.texture_units[0].texture_2d = old_tex;
+ cur_state.Apply();
+
+ if (res_scale != 1) {
+ auto scaled_rect = rect;
+ scaled_rect.left *= res_scale;
+ scaled_rect.top *= res_scale;
+ scaled_rect.right *= res_scale;
+ scaled_rect.bottom *= res_scale;
+
+ BlitTextures(unscaled_tex.handle, {0, rect.GetHeight(), rect.GetWidth(), 0}, texture.handle,
+ scaled_rect, type, read_fb_handle, draw_fb_handle);
+ }
+}
+
+MICROPROFILE_DEFINE(OpenGL_TextureDL, "OpenGL", "Texture Download", MP_RGB(128, 192, 64));
+void CachedSurface::DownloadGLTexture(const MathUtil::Rectangle<u32>& rect, GLuint read_fb_handle,
+ GLuint draw_fb_handle) {
+ if (type == SurfaceType::Fill)
+ return;
+
+ MICROPROFILE_SCOPE(OpenGL_TextureDL);
+
+ if (gl_buffer == nullptr) {
+ gl_buffer_size = width * height * GetGLBytesPerPixel(pixel_format);
+ gl_buffer.reset(new u8[gl_buffer_size]);
+ }
+
+ OpenGLState state = OpenGLState::GetCurState();
+ OpenGLState prev_state = state;
+ SCOPE_EXIT({ prev_state.Apply(); });
+
+ const FormatTuple& tuple = GetFormatTuple(pixel_format, component_type);
+
+ // Ensure no bad interactions with GL_PACK_ALIGNMENT
+ ASSERT(stride * GetGLBytesPerPixel(pixel_format) % 4 == 0);
+ glPixelStorei(GL_PACK_ROW_LENGTH, static_cast<GLint>(stride));
+ size_t buffer_offset = (rect.bottom * stride + rect.left) * GetGLBytesPerPixel(pixel_format);
+
+ // If not 1x scale, blit scaled texture to a new 1x texture and use that to flush
+ if (res_scale != 1) {
+ auto scaled_rect = rect;
+ scaled_rect.left *= res_scale;
+ scaled_rect.top *= res_scale;
+ scaled_rect.right *= res_scale;
+ scaled_rect.bottom *= res_scale;
+
+ OGLTexture unscaled_tex;
+ unscaled_tex.Create();
+
+ MathUtil::Rectangle<u32> unscaled_tex_rect{0, rect.GetHeight(), rect.GetWidth(), 0};
+ AllocateSurfaceTexture(unscaled_tex.handle, tuple, rect.GetWidth(), rect.GetHeight());
+ BlitTextures(texture.handle, scaled_rect, unscaled_tex.handle, unscaled_tex_rect, type,
+ read_fb_handle, draw_fb_handle);
+
+ state.texture_units[0].texture_2d = unscaled_tex.handle;
+ state.Apply();
+
+ glActiveTexture(GL_TEXTURE0);
+ glGetTexImage(GL_TEXTURE_2D, 0, tuple.format, tuple.type, &gl_buffer[buffer_offset]);
+ } else {
+ state.ResetTexture(texture.handle);
+ state.draw.read_framebuffer = read_fb_handle;
+ state.Apply();
+
+ if (type == SurfaceType::ColorTexture) {
+ glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ texture.handle, 0);
+ glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D,
+ 0, 0);
+ } else if (type == SurfaceType::Depth) {
+ glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
+ glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D,
+ texture.handle, 0);
+ glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
+ } else {
+ glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
+ glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D,
+ texture.handle, 0);
+ }
+ glReadPixels(static_cast<GLint>(rect.left), static_cast<GLint>(rect.bottom),
+ static_cast<GLsizei>(rect.GetWidth()), static_cast<GLsizei>(rect.GetHeight()),
+ tuple.format, tuple.type, &gl_buffer[buffer_offset]);
+ }
+
+ glPixelStorei(GL_PACK_ROW_LENGTH, 0);
+}
+
+enum class MatchFlags {
+ None = 0,
+ Invalid = 1, // Flag that can be applied to other match types, invalid matches require
+ // validation before they can be used
+ Exact = 1 << 1, // Surfaces perfectly match
+ SubRect = 1 << 2, // Surface encompasses params
+ Copy = 1 << 3, // Surface we can copy from
+ Expand = 1 << 4, // Surface that can expand params
+ TexCopy = 1 << 5 // Surface that will match a display transfer "texture copy" parameters
+};
+
+constexpr MatchFlags operator|(MatchFlags lhs, MatchFlags rhs) {
+ return static_cast<MatchFlags>(static_cast<int>(lhs) | static_cast<int>(rhs));
+}
+
+constexpr MatchFlags operator&(MatchFlags lhs, MatchFlags rhs) {
+ return static_cast<MatchFlags>(static_cast<int>(lhs) & static_cast<int>(rhs));
+}
+
+/// Get the best surface match (and its match type) for the given flags
+template <MatchFlags find_flags>
+Surface FindMatch(const SurfaceCache& surface_cache, const SurfaceParams& params,
+ ScaleMatch match_scale_type,
+ boost::optional<SurfaceInterval> validate_interval = boost::none) {
+ Surface match_surface = nullptr;
+ bool match_valid = false;
+ u32 match_scale = 0;
+ SurfaceInterval match_interval{};
+
+ for (auto& pair : RangeFromInterval(surface_cache, params.GetInterval())) {
+ for (auto& surface : pair.second) {
+ bool res_scale_matched = match_scale_type == ScaleMatch::Exact
+ ? (params.res_scale == surface->res_scale)
+ : (params.res_scale <= surface->res_scale);
+ // validity will be checked in GetCopyableInterval
+ bool is_valid =
+ (find_flags & MatchFlags::Copy) != MatchFlags::None
+ ? true
+ : surface->IsRegionValid(validate_interval.value_or(params.GetInterval()));
+
+ if ((find_flags & MatchFlags::Invalid) == MatchFlags::None && !is_valid)
+ continue;
+
+ auto IsMatch_Helper = [&](auto check_type, auto match_fn) {
+ if ((find_flags & check_type) == MatchFlags::None)
+ return;
+
+ bool matched;
+ SurfaceInterval surface_interval;
+ std::tie(matched, surface_interval) = match_fn();
+ if (!matched)
+ return;
+
+ if (!res_scale_matched && match_scale_type != ScaleMatch::Ignore &&
+ surface->type != SurfaceType::Fill)
+ return;
+
+ // Found a match, update only if this is better than the previous one
+ auto UpdateMatch = [&] {
+ match_surface = surface;
+ match_valid = is_valid;
+ match_scale = surface->res_scale;
+ match_interval = surface_interval;
+ };
+
+ if (surface->res_scale > match_scale) {
+ UpdateMatch();
+ return;
+ } else if (surface->res_scale < match_scale) {
+ return;
+ }
+
+ if (is_valid && !match_valid) {
+ UpdateMatch();
+ return;
+ } else if (is_valid != match_valid) {
+ return;
+ }
+
+ if (boost::icl::length(surface_interval) > boost::icl::length(match_interval)) {
+ UpdateMatch();
+ }
+ };
+ IsMatch_Helper(std::integral_constant<MatchFlags, MatchFlags::Exact>{}, [&] {
+ return std::make_pair(surface->ExactMatch(params), surface->GetInterval());
+ });
+ IsMatch_Helper(std::integral_constant<MatchFlags, MatchFlags::SubRect>{}, [&] {
+ return std::make_pair(surface->CanSubRect(params), surface->GetInterval());
+ });
+ IsMatch_Helper(std::integral_constant<MatchFlags, MatchFlags::Copy>{}, [&] {
+ auto copy_interval =
+ params.FromInterval(*validate_interval).GetCopyableInterval(surface);
+ bool matched = boost::icl::length(copy_interval & *validate_interval) != 0 &&
+ surface->CanCopy(params, copy_interval);
+ return std::make_pair(matched, copy_interval);
+ });
+ IsMatch_Helper(std::integral_constant<MatchFlags, MatchFlags::Expand>{}, [&] {
+ return std::make_pair(surface->CanExpand(params), surface->GetInterval());
+ });
+ IsMatch_Helper(std::integral_constant<MatchFlags, MatchFlags::TexCopy>{}, [&] {
+ return std::make_pair(surface->CanTexCopy(params), surface->GetInterval());
+ });
+ }
+ }
+ return match_surface;
+}
+
+RasterizerCacheOpenGL::RasterizerCacheOpenGL() {
+ read_framebuffer.Create();
+ draw_framebuffer.Create();
+
+ attributeless_vao.Create();
+
+ d24s8_abgr_buffer.Create();
+ d24s8_abgr_buffer_size = 0;
+
+ const char* vs_source = R"(
+#version 330 core
+const vec2 vertices[4] = vec2[4](vec2(-1.0, -1.0), vec2(1.0, -1.0), vec2(-1.0, 1.0), vec2(1.0, 1.0));
+void main() {
+ gl_Position = vec4(vertices[gl_VertexID], 0.0, 1.0);
+}
+)";
+ const char* fs_source = R"(
+#version 330 core
+
+uniform samplerBuffer tbo;
+uniform vec2 tbo_size;
+uniform vec4 viewport;
+
+out vec4 color;
+
+void main() {
+ vec2 tbo_coord = (gl_FragCoord.xy - viewport.xy) * tbo_size / viewport.zw;
+ int tbo_offset = int(tbo_coord.y) * int(tbo_size.x) + int(tbo_coord.x);
+ color = texelFetch(tbo, tbo_offset).rabg;
+}
+)";
+ d24s8_abgr_shader.CreateFromSource(vs_source, nullptr, fs_source);
+
+ OpenGLState state = OpenGLState::GetCurState();
+ GLuint old_program = state.draw.shader_program;
+ state.draw.shader_program = d24s8_abgr_shader.handle;
+ state.Apply();
+
+ GLint tbo_u_id = glGetUniformLocation(d24s8_abgr_shader.handle, "tbo");
+ ASSERT(tbo_u_id != -1);
+ glUniform1i(tbo_u_id, 0);
+
+ state.draw.shader_program = old_program;
+ state.Apply();
+
+ d24s8_abgr_tbo_size_u_id = glGetUniformLocation(d24s8_abgr_shader.handle, "tbo_size");
+ ASSERT(d24s8_abgr_tbo_size_u_id != -1);
+ d24s8_abgr_viewport_u_id = glGetUniformLocation(d24s8_abgr_shader.handle, "viewport");
+ ASSERT(d24s8_abgr_viewport_u_id != -1);
+}
+
+RasterizerCacheOpenGL::~RasterizerCacheOpenGL() {
+ FlushAll();
+ while (!surface_cache.empty())
+ UnregisterSurface(*surface_cache.begin()->second.begin());
+}
+
+bool RasterizerCacheOpenGL::BlitSurfaces(const Surface& src_surface,
+ const MathUtil::Rectangle<u32>& src_rect,
+ const Surface& dst_surface,
+ const MathUtil::Rectangle<u32>& dst_rect) {
+ if (!SurfaceParams::CheckFormatsBlittable(src_surface->pixel_format, dst_surface->pixel_format))
+ return false;
+
+ return BlitTextures(src_surface->texture.handle, src_rect, dst_surface->texture.handle,
+ dst_rect, src_surface->type, read_framebuffer.handle,
+ draw_framebuffer.handle);
+}
+
+void RasterizerCacheOpenGL::ConvertD24S8toABGR(GLuint src_tex,
+ const MathUtil::Rectangle<u32>& src_rect,
+ GLuint dst_tex,
+ const MathUtil::Rectangle<u32>& dst_rect) {
+ OpenGLState prev_state = OpenGLState::GetCurState();
+ SCOPE_EXIT({ prev_state.Apply(); });
+
+ OpenGLState state;
+ state.draw.read_framebuffer = read_framebuffer.handle;
+ state.draw.draw_framebuffer = draw_framebuffer.handle;
+ state.Apply();
+
+ glBindBuffer(GL_PIXEL_PACK_BUFFER, d24s8_abgr_buffer.handle);
+
+ GLsizeiptr target_pbo_size = src_rect.GetWidth() * src_rect.GetHeight() * 4;
+ if (target_pbo_size > d24s8_abgr_buffer_size) {
+ d24s8_abgr_buffer_size = target_pbo_size * 2;
+ glBufferData(GL_PIXEL_PACK_BUFFER, d24s8_abgr_buffer_size, nullptr, GL_STREAM_COPY);
+ }
+
+ glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
+ glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, src_tex,
+ 0);
+ glReadPixels(static_cast<GLint>(src_rect.left), static_cast<GLint>(src_rect.bottom),
+ static_cast<GLsizei>(src_rect.GetWidth()),
+ static_cast<GLsizei>(src_rect.GetHeight()), GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8,
+ 0);
+
+ glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
+
+ // PBO now contains src_tex in RABG format
+ state.draw.shader_program = d24s8_abgr_shader.handle;
+ state.draw.vertex_array = attributeless_vao.handle;
+ state.viewport.x = static_cast<GLint>(dst_rect.left);
+ state.viewport.y = static_cast<GLint>(dst_rect.bottom);
+ state.viewport.width = static_cast<GLsizei>(dst_rect.GetWidth());
+ state.viewport.height = static_cast<GLsizei>(dst_rect.GetHeight());
+ state.Apply();
+
+ OGLTexture tbo;
+ tbo.Create();
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_BUFFER, tbo.handle);
+ glTexBuffer(GL_TEXTURE_BUFFER, GL_RGBA8, d24s8_abgr_buffer.handle);
+
+ glUniform2f(d24s8_abgr_tbo_size_u_id, static_cast<GLfloat>(src_rect.GetWidth()),
+ static_cast<GLfloat>(src_rect.GetHeight()));
+ glUniform4f(d24s8_abgr_viewport_u_id, static_cast<GLfloat>(state.viewport.x),
+ static_cast<GLfloat>(state.viewport.y), static_cast<GLfloat>(state.viewport.width),
+ static_cast<GLfloat>(state.viewport.height));
+
+ glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst_tex, 0);
+ glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
+ glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+
+ glBindTexture(GL_TEXTURE_BUFFER, 0);
+}
+
+Surface RasterizerCacheOpenGL::GetSurface(const SurfaceParams& params, ScaleMatch match_res_scale,
+ bool load_if_create) {
+ if (params.addr == 0 || params.height * params.width == 0) {
+ return nullptr;
+ }
+ // Use GetSurfaceSubRect instead
+ ASSERT(params.width == params.stride);
+
+ // Check for an exact match in existing surfaces
+ Surface surface =
+ FindMatch<MatchFlags::Exact | MatchFlags::Invalid>(surface_cache, params, match_res_scale);
+
+ if (surface == nullptr) {
+ u16 target_res_scale = params.res_scale;
+ if (match_res_scale != ScaleMatch::Exact) {
+ // This surface may have a subrect of another surface with a higher res_scale, find it
+ // to adjust our params
+ SurfaceParams find_params = params;
+ Surface expandable = FindMatch<MatchFlags::Expand | MatchFlags::Invalid>(
+ surface_cache, find_params, match_res_scale);
+ if (expandable != nullptr && expandable->res_scale > target_res_scale) {
+ target_res_scale = expandable->res_scale;
+ }
+ }
+ SurfaceParams new_params = params;
+ new_params.res_scale = target_res_scale;
+ surface = CreateSurface(new_params);
+ RegisterSurface(surface);
+ }
+
+ if (load_if_create) {
+ ValidateSurface(surface, params.addr, params.size);
+ }
+
+ return surface;
+}
+
+boost::optional<Tegra::GPUVAddr> RasterizerCacheOpenGL::TryFindFramebufferGpuAddress(
+ VAddr cpu_addr) const {
+ // Tries to find the GPU address of a framebuffer based on the CPU address. This is because
+ // final output framebuffers are specified by CPU address, but internally our GPU cache uses GPU
+ // addresses. We iterate through all cached framebuffers, and compare their starting CPU address
+ // to the one provided. This is obviously not great, and won't work if the framebuffer overlaps
+ // surfaces.
+
+ std::vector<Tegra::GPUVAddr> gpu_addresses;
+ for (const auto& pair : surface_cache) {
+ for (const auto& surface : pair.second) {
+ const VAddr surface_cpu_addr = surface->GetCpuAddr();
+ if (cpu_addr >= surface_cpu_addr && cpu_addr < (surface_cpu_addr + surface->size)) {
+ ASSERT_MSG(cpu_addr == surface_cpu_addr, "overlapping surfaces are unsupported");
+ gpu_addresses.push_back(surface->addr);
+ }
+ }
+ }
+
+ if (gpu_addresses.empty()) {
+ return {};
+ }
+
+ ASSERT_MSG(gpu_addresses.size() == 1, ">1 surface is unsupported");
+ return gpu_addresses[0];
+}
+
+SurfaceRect_Tuple RasterizerCacheOpenGL::GetSurfaceSubRect(const SurfaceParams& params,
+ ScaleMatch match_res_scale,
+ bool load_if_create) {
+ if (params.addr == 0 || params.height * params.width == 0) {
+ return std::make_tuple(nullptr, MathUtil::Rectangle<u32>{});
+ }
+
+ // Attempt to find encompassing surface
+ Surface surface = FindMatch<MatchFlags::SubRect | MatchFlags::Invalid>(surface_cache, params,
+ match_res_scale);
+
+ // Check if FindMatch failed because of res scaling
+ // If that's the case create a new surface with
+ // the dimensions of the lower res_scale surface
+ // to suggest it should not be used again
+ if (surface == nullptr && match_res_scale != ScaleMatch::Ignore) {
+ surface = FindMatch<MatchFlags::SubRect | MatchFlags::Invalid>(surface_cache, params,
+ ScaleMatch::Ignore);
+ if (surface != nullptr) {
+ ASSERT(surface->res_scale < params.res_scale);
+ SurfaceParams new_params = *surface;
+ new_params.res_scale = params.res_scale;
+
+ surface = CreateSurface(new_params);
+ RegisterSurface(surface);
+ }
+ }
+
+ SurfaceParams aligned_params = params;
+ if (params.is_tiled) {
+ aligned_params.height = Common::AlignUp(params.height, 8);
+ aligned_params.width = Common::AlignUp(params.width, 8);
+ aligned_params.stride = Common::AlignUp(params.stride, 8);
+ aligned_params.UpdateParams();
+ }
+
+ // Check for a surface we can expand before creating a new one
+ if (surface == nullptr) {
+ surface = FindMatch<MatchFlags::Expand | MatchFlags::Invalid>(surface_cache, aligned_params,
+ match_res_scale);
+ if (surface != nullptr) {
+ aligned_params.width = aligned_params.stride;
+ aligned_params.UpdateParams();
+
+ SurfaceParams new_params = *surface;
+ new_params.addr = std::min(aligned_params.addr, surface->addr);
+ new_params.end = std::max(aligned_params.end, surface->end);
+ new_params.size = new_params.end - new_params.addr;
+ new_params.height = static_cast<u32>(
+ new_params.size / aligned_params.BytesInPixels(aligned_params.stride));
+ ASSERT(new_params.size % aligned_params.BytesInPixels(aligned_params.stride) == 0);
+
+ Surface new_surface = CreateSurface(new_params);
+ DuplicateSurface(surface, new_surface);
+
+ // Delete the expanded surface, this can't be done safely yet
+ // because it may still be in use
+ remove_surfaces.emplace(surface);
+
+ surface = new_surface;
+ RegisterSurface(new_surface);
+ }
+ }
+
+ // No subrect found - create and return a new surface
+ if (surface == nullptr) {
+ SurfaceParams new_params = aligned_params;
+ // Can't have gaps in a surface
+ new_params.width = aligned_params.stride;
+ new_params.UpdateParams();
+ // GetSurface will create the new surface and possibly adjust res_scale if necessary
+ surface = GetSurface(new_params, match_res_scale, load_if_create);
+ } else if (load_if_create) {
+ ValidateSurface(surface, aligned_params.addr, aligned_params.size);
+ }
+
+ return std::make_tuple(surface, surface->GetScaledSubRect(params));
+}
+
+Surface RasterizerCacheOpenGL::GetTextureSurface(const Tegra::Texture::FullTextureInfo& config) {
+ auto& gpu = Core::System::GetInstance().GPU();
+
+ SurfaceParams params;
+ params.addr = config.tic.Address();
+ params.is_tiled = config.tic.IsTiled();
+ params.pixel_format = SurfaceParams::PixelFormatFromTextureFormat(config.tic.format);
+
+ params.width = Common::AlignUp(config.tic.Width(), params.GetCompresssionFactor()) /
+ params.GetCompresssionFactor();
+ params.height = Common::AlignUp(config.tic.Height(), params.GetCompresssionFactor()) /
+ params.GetCompresssionFactor();
+
+ // TODO(Subv): Different types per component are not supported.
+ ASSERT(config.tic.r_type.Value() == config.tic.g_type.Value() &&
+ config.tic.r_type.Value() == config.tic.b_type.Value() &&
+ config.tic.r_type.Value() == config.tic.a_type.Value());
+
+ params.component_type = SurfaceParams::ComponentTypeFromTexture(config.tic.r_type.Value());
+
+ if (config.tic.IsTiled()) {
+ params.block_height = config.tic.BlockHeight();
+
+ // TODO(bunnei): The below align up is a hack. This is here because some compressed textures
+ // are not a multiple of their own compression factor, and so this accounts for that. This
+ // could potentially result in an extra row of 4px being decoded if a texture is not a
+ // multiple of 4.
+ params.width = Common::AlignUp(params.width, 4);
+ params.height = Common::AlignUp(params.height, 4);
+ } else {
+ // Use the texture-provided stride value if the texture isn't tiled.
+ params.stride = static_cast<u32>(params.PixelsInBytes(config.tic.Pitch()));
+ }
+
+ params.UpdateParams();
+
+ return GetSurface(params, ScaleMatch::Ignore, true);
+}
+
+SurfaceSurfaceRect_Tuple RasterizerCacheOpenGL::GetFramebufferSurfaces(
+ bool using_color_fb, bool using_depth_fb, const MathUtil::Rectangle<s32>& viewport) {
+ const auto& regs = Core::System().GetInstance().GPU().Maxwell3D().regs;
+ const auto& config = regs.rt[0];
+
+ // TODO(bunnei): This is hard corded to use just the first render buffer
+ NGLOG_WARNING(Render_OpenGL, "hard-coded for render target 0!");
+
+ // update resolution_scale_factor and reset cache if changed
+ // TODO (bunnei): This code was ported as-is from Citra, and is technically not thread-safe. We
+ // need to fix this before making the renderer multi-threaded.
+ static u16 resolution_scale_factor = GetResolutionScaleFactor();
+ if (resolution_scale_factor != GetResolutionScaleFactor()) {
+ resolution_scale_factor = GetResolutionScaleFactor();
+ FlushAll();
+ while (!surface_cache.empty())
+ UnregisterSurface(*surface_cache.begin()->second.begin());
+ }
+
+ MathUtil::Rectangle<u32> viewport_clamped{
+ static_cast<u32>(std::clamp(viewport.left, 0, static_cast<s32>(config.width))),
+ static_cast<u32>(std::clamp(viewport.top, 0, static_cast<s32>(config.height))),
+ static_cast<u32>(std::clamp(viewport.right, 0, static_cast<s32>(config.width))),
+ static_cast<u32>(std::clamp(viewport.bottom, 0, static_cast<s32>(config.height)))};
+
+ // get color and depth surfaces
+ SurfaceParams color_params;
+ color_params.is_tiled = true;
+ color_params.res_scale = resolution_scale_factor;
+ color_params.width = config.width;
+ color_params.height = config.height;
+ // TODO(Subv): Can framebuffers use a different block height?
+ color_params.block_height = Tegra::Texture::TICEntry::DefaultBlockHeight;
+ SurfaceParams depth_params = color_params;
+
+ color_params.addr = config.Address();
+ color_params.pixel_format = SurfaceParams::PixelFormatFromRenderTargetFormat(config.format);
+ color_params.component_type = SurfaceParams::ComponentTypeFromRenderTarget(config.format);
+ color_params.UpdateParams();
+
+ ASSERT_MSG(!using_depth_fb, "depth buffer is unimplemented");
+ // depth_params.addr = config.GetDepthBufferPhysicalAddress();
+ // depth_params.pixel_format = SurfaceParams::PixelFormatFromDepthFormat(config.depth_format);
+ // depth_params.UpdateParams();
+
+ auto color_vp_interval = color_params.GetSubRectInterval(viewport_clamped);
+ auto depth_vp_interval = depth_params.GetSubRectInterval(viewport_clamped);
+
+ // Make sure that framebuffers don't overlap if both color and depth are being used
+ if (using_color_fb && using_depth_fb &&
+ boost::icl::length(color_vp_interval & depth_vp_interval)) {
+ NGLOG_CRITICAL(Render_OpenGL, "Color and depth framebuffer memory regions overlap; "
+ "overlapping framebuffers not supported!");
+ using_depth_fb = false;
+ }
+
+ MathUtil::Rectangle<u32> color_rect{};
+ Surface color_surface = nullptr;
+ if (using_color_fb)
+ std::tie(color_surface, color_rect) =
+ GetSurfaceSubRect(color_params, ScaleMatch::Exact, false);
+
+ MathUtil::Rectangle<u32> depth_rect{};
+ Surface depth_surface = nullptr;
+ if (using_depth_fb)
+ std::tie(depth_surface, depth_rect) =
+ GetSurfaceSubRect(depth_params, ScaleMatch::Exact, false);
+
+ MathUtil::Rectangle<u32> fb_rect{};
+ if (color_surface != nullptr && depth_surface != nullptr) {
+ fb_rect = color_rect;
+ // Color and Depth surfaces must have the same dimensions and offsets
+ if (color_rect.bottom != depth_rect.bottom || color_rect.top != depth_rect.top ||
+ color_rect.left != depth_rect.left || color_rect.right != depth_rect.right) {
+ color_surface = GetSurface(color_params, ScaleMatch::Exact, false);
+ depth_surface = GetSurface(depth_params, ScaleMatch::Exact, false);
+ fb_rect = color_surface->GetScaledRect();
+ }
+ } else if (color_surface != nullptr) {
+ fb_rect = color_rect;
+ } else if (depth_surface != nullptr) {
+ fb_rect = depth_rect;
+ }
+
+ if (color_surface != nullptr) {
+ ValidateSurface(color_surface, boost::icl::first(color_vp_interval),
+ boost::icl::length(color_vp_interval));
+ }
+ if (depth_surface != nullptr) {
+ ValidateSurface(depth_surface, boost::icl::first(depth_vp_interval),
+ boost::icl::length(depth_vp_interval));
+ }
+
+ return std::make_tuple(color_surface, depth_surface, fb_rect);
+}
+
+Surface RasterizerCacheOpenGL::GetFillSurface(const void* config) {
+ UNREACHABLE();
+ return {};
+}
+
+SurfaceRect_Tuple RasterizerCacheOpenGL::GetTexCopySurface(const SurfaceParams& params) {
+ MathUtil::Rectangle<u32> rect{};
+
+ Surface match_surface = FindMatch<MatchFlags::TexCopy | MatchFlags::Invalid>(
+ surface_cache, params, ScaleMatch::Ignore);
+
+ if (match_surface != nullptr) {
+ ValidateSurface(match_surface, params.addr, params.size);
+
+ SurfaceParams match_subrect;
+ if (params.width != params.stride) {
+ const u32 tiled_size = match_surface->is_tiled ? 8 : 1;
+ match_subrect = params;
+ match_subrect.width =
+ static_cast<u32>(match_surface->PixelsInBytes(params.width) / tiled_size);
+ match_subrect.stride =
+ static_cast<u32>(match_surface->PixelsInBytes(params.stride) / tiled_size);
+ match_subrect.height *= tiled_size;
+ } else {
+ match_subrect = match_surface->FromInterval(params.GetInterval());
+ ASSERT(match_subrect.GetInterval() == params.GetInterval());
+ }
+
+ rect = match_surface->GetScaledSubRect(match_subrect);
+ }
+
+ return std::make_tuple(match_surface, rect);
+}
+
+void RasterizerCacheOpenGL::DuplicateSurface(const Surface& src_surface,
+ const Surface& dest_surface) {
+ ASSERT(dest_surface->addr <= src_surface->addr && dest_surface->end >= src_surface->end);
+
+ BlitSurfaces(src_surface, src_surface->GetScaledRect(), dest_surface,
+ dest_surface->GetScaledSubRect(*src_surface));
+
+ dest_surface->invalid_regions -= src_surface->GetInterval();
+ dest_surface->invalid_regions += src_surface->invalid_regions;
+
+ SurfaceRegions regions;
+ for (auto& pair : RangeFromInterval(dirty_regions, src_surface->GetInterval())) {
+ if (pair.second == src_surface) {
+ regions += pair.first;
+ }
+ }
+ for (auto& interval : regions) {
+ dirty_regions.set({interval, dest_surface});
+ }
+}
+
+void RasterizerCacheOpenGL::ValidateSurface(const Surface& surface, Tegra::GPUVAddr addr,
+ u64 size) {
+ if (size == 0)
+ return;
+
+ const SurfaceInterval validate_interval(addr, addr + size);
+
+ if (surface->type == SurfaceType::Fill) {
+ // Sanity check, fill surfaces will always be valid when used
+ ASSERT(surface->IsRegionValid(validate_interval));
+ return;
+ }
+
+ while (true) {
+ const auto it = surface->invalid_regions.find(validate_interval);
+ if (it == surface->invalid_regions.end())
+ break;
+
+ const auto interval = *it & validate_interval;
+ // Look for a valid surface to copy from
+ SurfaceParams params = *surface;
+
+ Surface copy_surface =
+ FindMatch<MatchFlags::Copy>(surface_cache, params, ScaleMatch::Ignore, interval);
+ if (copy_surface != nullptr) {
+ SurfaceInterval copy_interval = params.GetCopyableInterval(copy_surface);
+ CopySurface(copy_surface, surface, copy_interval);
+ surface->invalid_regions.erase(copy_interval);
+ continue;
+ }
+
+ // Load data from Switch memory
+ FlushRegion(params.addr, params.size);
+ surface->LoadGLBuffer(params.addr, params.end);
+ surface->UploadGLTexture(surface->GetSubRect(params), read_framebuffer.handle,
+ draw_framebuffer.handle);
+ surface->invalid_regions.erase(params.GetInterval());
+ }
+}
+
+void RasterizerCacheOpenGL::FlushRegion(Tegra::GPUVAddr addr, u64 size, Surface flush_surface) {
+ if (size == 0)
+ return;
+
+ const SurfaceInterval flush_interval(addr, addr + size);
+ SurfaceRegions flushed_intervals;
+
+ for (auto& pair : RangeFromInterval(dirty_regions, flush_interval)) {
+ // small sizes imply that this most likely comes from the cpu, flush the entire region
+ // the point is to avoid thousands of small writes every frame if the cpu decides to access
+ // that region, anything higher than 8 you're guaranteed it comes from a service
+ const auto interval = size <= 8 ? pair.first : pair.first & flush_interval;
+ auto& surface = pair.second;
+
+ if (flush_surface != nullptr && surface != flush_surface)
+ continue;
+
+ // Sanity check, this surface is the last one that marked this region dirty
+ ASSERT(surface->IsRegionValid(interval));
+
+ if (surface->type != SurfaceType::Fill) {
+ SurfaceParams params = surface->FromInterval(interval);
+ surface->DownloadGLTexture(surface->GetSubRect(params), read_framebuffer.handle,
+ draw_framebuffer.handle);
+ }
+ surface->FlushGLBuffer(boost::icl::first(interval), boost::icl::last_next(interval));
+ flushed_intervals += interval;
+ }
+ // Reset dirty regions
+ dirty_regions -= flushed_intervals;
+}
+
+void RasterizerCacheOpenGL::FlushAll() {
+ FlushRegion(0, Kernel::VMManager::MAX_ADDRESS);
+}
+
+void RasterizerCacheOpenGL::InvalidateRegion(Tegra::GPUVAddr addr, u64 size,
+ const Surface& region_owner) {
+ if (size == 0)
+ return;
+
+ const SurfaceInterval invalid_interval(addr, addr + size);
+
+ if (region_owner != nullptr) {
+ ASSERT(addr >= region_owner->addr && addr + size <= region_owner->end);
+ // Surfaces can't have a gap
+ ASSERT(region_owner->width == region_owner->stride);
+ region_owner->invalid_regions.erase(invalid_interval);
+ }
+
+ for (auto& pair : RangeFromInterval(surface_cache, invalid_interval)) {
+ for (auto& cached_surface : pair.second) {
+ if (cached_surface == region_owner)
+ continue;
+
+ // If cpu is invalidating this region we want to remove it
+ // to (likely) mark the memory pages as uncached
+ if (region_owner == nullptr && size <= 8) {
+ FlushRegion(cached_surface->addr, cached_surface->size, cached_surface);
+ remove_surfaces.emplace(cached_surface);
+ continue;
+ }
+
+ const auto interval = cached_surface->GetInterval() & invalid_interval;
+ cached_surface->invalid_regions.insert(interval);
+
+ // Remove only "empty" fill surfaces to avoid destroying and recreating OGL textures
+ if (cached_surface->type == SurfaceType::Fill &&
+ cached_surface->IsSurfaceFullyInvalid()) {
+ remove_surfaces.emplace(cached_surface);
+ }
+ }
+ }
+
+ if (region_owner != nullptr)
+ dirty_regions.set({invalid_interval, region_owner});
+ else
+ dirty_regions.erase(invalid_interval);
+
+ for (auto& remove_surface : remove_surfaces) {
+ if (remove_surface == region_owner) {
+ Surface expanded_surface = FindMatch<MatchFlags::SubRect | MatchFlags::Invalid>(
+ surface_cache, *region_owner, ScaleMatch::Ignore);
+ ASSERT(expanded_surface);
+
+ if ((region_owner->invalid_regions - expanded_surface->invalid_regions).empty()) {
+ DuplicateSurface(region_owner, expanded_surface);
+ } else {
+ continue;
+ }
+ }
+ UnregisterSurface(remove_surface);
+ }
+
+ remove_surfaces.clear();
+}
+
+Surface RasterizerCacheOpenGL::CreateSurface(const SurfaceParams& params) {
+ Surface surface = std::make_shared<CachedSurface>();
+ static_cast<SurfaceParams&>(*surface) = params;
+
+ surface->texture.Create();
+
+ surface->gl_buffer_size = 0;
+ surface->invalid_regions.insert(surface->GetInterval());
+ AllocateSurfaceTexture(surface->texture.handle,
+ GetFormatTuple(surface->pixel_format, surface->component_type),
+ surface->GetScaledWidth(), surface->GetScaledHeight());
+
+ return surface;
+}
+
+void RasterizerCacheOpenGL::RegisterSurface(const Surface& surface) {
+ if (surface->registered) {
+ return;
+ }
+ surface->registered = true;
+ surface_cache.add({surface->GetInterval(), SurfaceSet{surface}});
+ UpdatePagesCachedCount(surface->addr, surface->size, 1);
+}
+
+void RasterizerCacheOpenGL::UnregisterSurface(const Surface& surface) {
+ if (!surface->registered) {
+ return;
+ }
+ surface->registered = false;
+ UpdatePagesCachedCount(surface->addr, surface->size, -1);
+ surface_cache.subtract({surface->GetInterval(), SurfaceSet{surface}});
+}
+
+void RasterizerCacheOpenGL::UpdatePagesCachedCount(Tegra::GPUVAddr addr, u64 size, int delta) {
+ const u64 num_pages = ((addr + size - 1) >> Tegra::MemoryManager::PAGE_BITS) -
+ (addr >> Tegra::MemoryManager::PAGE_BITS) + 1;
+ const u64 page_start = addr >> Tegra::MemoryManager::PAGE_BITS;
+ const u64 page_end = page_start + num_pages;
+
+ // Interval maps will erase segments if count reaches 0, so if delta is negative we have to
+ // subtract after iterating
+ const auto pages_interval = PageMap::interval_type::right_open(page_start, page_end);
+ if (delta > 0)
+ cached_pages.add({pages_interval, delta});
+
+ for (const auto& pair : RangeFromInterval(cached_pages, pages_interval)) {
+ const auto interval = pair.first & pages_interval;
+ const int count = pair.second;
+
+ const Tegra::GPUVAddr interval_start_addr = boost::icl::first(interval)
+ << Tegra::MemoryManager::PAGE_BITS;
+ const Tegra::GPUVAddr interval_end_addr = boost::icl::last_next(interval)
+ << Tegra::MemoryManager::PAGE_BITS;
+ const u64 interval_size = interval_end_addr - interval_start_addr;
+
+ if (delta > 0 && count == delta)
+ Memory::RasterizerMarkRegionCached(interval_start_addr, interval_size, true);
+ else if (delta < 0 && count == -delta)
+ Memory::RasterizerMarkRegionCached(interval_start_addr, interval_size, false);
+ else
+ ASSERT(count >= 0);
+ }
+
+ if (delta < 0)
+ cached_pages.add({pages_interval, delta});
+}
diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.h b/src/video_core/renderer_opengl/gl_rasterizer_cache.h
new file mode 100644
index 000000000..0f43e863d
--- /dev/null
+++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.h
@@ -0,0 +1,520 @@
+// Copyright 2015 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <memory>
+#include <set>
+#include <tuple>
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-local-typedefs"
+#endif
+#include <boost/icl/interval_map.hpp>
+#include <boost/icl/interval_set.hpp>
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+#include <boost/optional.hpp>
+#include <glad/glad.h>
+#include "common/assert.h"
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "common/math_util.h"
+#include "video_core/gpu.h"
+#include "video_core/memory_manager.h"
+#include "video_core/renderer_opengl/gl_resource_manager.h"
+#include "video_core/textures/texture.h"
+
+struct CachedSurface;
+using Surface = std::shared_ptr<CachedSurface>;
+using SurfaceSet = std::set<Surface>;
+
+using SurfaceRegions = boost::icl::interval_set<Tegra::GPUVAddr>;
+using SurfaceMap = boost::icl::interval_map<Tegra::GPUVAddr, Surface>;
+using SurfaceCache = boost::icl::interval_map<Tegra::GPUVAddr, SurfaceSet>;
+
+using SurfaceInterval = SurfaceCache::interval_type;
+static_assert(std::is_same<SurfaceRegions::interval_type, SurfaceCache::interval_type>() &&
+ std::is_same<SurfaceMap::interval_type, SurfaceCache::interval_type>(),
+ "incorrect interval types");
+
+using SurfaceRect_Tuple = std::tuple<Surface, MathUtil::Rectangle<u32>>;
+using SurfaceSurfaceRect_Tuple = std::tuple<Surface, Surface, MathUtil::Rectangle<u32>>;
+
+using PageMap = boost::icl::interval_map<u64, int>;
+
+enum class ScaleMatch {
+ Exact, // only accept same res scale
+ Upscale, // only allow higher scale than params
+ Ignore // accept every scaled res
+};
+
+struct SurfaceParams {
+ enum class PixelFormat {
+ ABGR8 = 0,
+ B5G6R5 = 1,
+ A2B10G10R10 = 2,
+ A1B5G5R5 = 3,
+ R8 = 4,
+ RGBA16F = 5,
+ R11FG11FB10F = 6,
+ DXT1 = 7,
+ DXT23 = 8,
+ DXT45 = 9,
+ DXN1 = 10, // This is also known as BC4
+
+ Max,
+ Invalid = 255,
+ };
+
+ static constexpr size_t MaxPixelFormat = static_cast<size_t>(PixelFormat::Max);
+
+ enum class ComponentType {
+ Invalid = 0,
+ SNorm = 1,
+ UNorm = 2,
+ SInt = 3,
+ UInt = 4,
+ Float = 5,
+ };
+
+ enum class SurfaceType {
+ ColorTexture = 0,
+ Depth = 1,
+ DepthStencil = 2,
+ Fill = 3,
+ Invalid = 4,
+ };
+
+ /**
+ * Gets the compression factor for the specified PixelFormat. This applies to just the
+ * "compressed width" and "compressed height", not the overall compression factor of a
+ * compressed image. This is used for maintaining proper surface sizes for compressed texture
+ * formats.
+ */
+ static constexpr u32 GetCompresssionFactor(PixelFormat format) {
+ if (format == PixelFormat::Invalid)
+ return 0;
+
+ constexpr std::array<u32, MaxPixelFormat> compression_factor_table = {{
+ 1, // ABGR8
+ 1, // B5G6R5
+ 1, // A2B10G10R10
+ 1, // A1B5G5R5
+ 1, // R8
+ 1, // RGBA16F
+ 1, // R11FG11FB10F
+ 4, // DXT1
+ 4, // DXT23
+ 4, // DXT45
+ 4, // DXN1
+ }};
+
+ ASSERT(static_cast<size_t>(format) < compression_factor_table.size());
+ return compression_factor_table[static_cast<size_t>(format)];
+ }
+ u32 GetCompresssionFactor() const {
+ return GetCompresssionFactor(pixel_format);
+ }
+
+ static constexpr u32 GetFormatBpp(PixelFormat format) {
+ if (format == PixelFormat::Invalid)
+ return 0;
+
+ constexpr std::array<u32, MaxPixelFormat> bpp_table = {{
+ 32, // ABGR8
+ 16, // B5G6R5
+ 32, // A2B10G10R10
+ 16, // A1B5G5R5
+ 8, // R8
+ 64, // RGBA16F
+ 32, // R11FG11FB10F
+ 64, // DXT1
+ 128, // DXT23
+ 128, // DXT45
+ 64, // DXN1
+ }};
+
+ ASSERT(static_cast<size_t>(format) < bpp_table.size());
+ return bpp_table[static_cast<size_t>(format)];
+ }
+ u32 GetFormatBpp() const {
+ return GetFormatBpp(pixel_format);
+ }
+
+ static PixelFormat PixelFormatFromRenderTargetFormat(Tegra::RenderTargetFormat format) {
+ switch (format) {
+ case Tegra::RenderTargetFormat::RGBA8_UNORM:
+ case Tegra::RenderTargetFormat::RGBA8_SRGB:
+ return PixelFormat::ABGR8;
+ case Tegra::RenderTargetFormat::RGB10_A2_UNORM:
+ return PixelFormat::A2B10G10R10;
+ case Tegra::RenderTargetFormat::RGBA16_FLOAT:
+ return PixelFormat::RGBA16F;
+ case Tegra::RenderTargetFormat::R11G11B10_FLOAT:
+ return PixelFormat::R11FG11FB10F;
+ default:
+ NGLOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format));
+ UNREACHABLE();
+ }
+ }
+
+ static PixelFormat PixelFormatFromGPUPixelFormat(Tegra::FramebufferConfig::PixelFormat format) {
+ switch (format) {
+ case Tegra::FramebufferConfig::PixelFormat::ABGR8:
+ return PixelFormat::ABGR8;
+ default:
+ NGLOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format));
+ UNREACHABLE();
+ }
+ }
+
+ static PixelFormat PixelFormatFromTextureFormat(Tegra::Texture::TextureFormat format) {
+ // TODO(Subv): Properly implement this
+ switch (format) {
+ case Tegra::Texture::TextureFormat::A8R8G8B8:
+ return PixelFormat::ABGR8;
+ case Tegra::Texture::TextureFormat::B5G6R5:
+ return PixelFormat::B5G6R5;
+ case Tegra::Texture::TextureFormat::A2B10G10R10:
+ return PixelFormat::A2B10G10R10;
+ case Tegra::Texture::TextureFormat::A1B5G5R5:
+ return PixelFormat::A1B5G5R5;
+ case Tegra::Texture::TextureFormat::R8:
+ return PixelFormat::R8;
+ case Tegra::Texture::TextureFormat::R16_G16_B16_A16:
+ return PixelFormat::RGBA16F;
+ case Tegra::Texture::TextureFormat::BF10GF11RF11:
+ return PixelFormat::R11FG11FB10F;
+ case Tegra::Texture::TextureFormat::DXT1:
+ return PixelFormat::DXT1;
+ case Tegra::Texture::TextureFormat::DXT23:
+ return PixelFormat::DXT23;
+ case Tegra::Texture::TextureFormat::DXT45:
+ return PixelFormat::DXT45;
+ case Tegra::Texture::TextureFormat::DXN1:
+ return PixelFormat::DXN1;
+ default:
+ NGLOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format));
+ UNREACHABLE();
+ }
+ }
+
+ static Tegra::Texture::TextureFormat TextureFormatFromPixelFormat(PixelFormat format) {
+ // TODO(Subv): Properly implement this
+ switch (format) {
+ case PixelFormat::ABGR8:
+ return Tegra::Texture::TextureFormat::A8R8G8B8;
+ case PixelFormat::B5G6R5:
+ return Tegra::Texture::TextureFormat::B5G6R5;
+ case PixelFormat::A2B10G10R10:
+ return Tegra::Texture::TextureFormat::A2B10G10R10;
+ case PixelFormat::A1B5G5R5:
+ return Tegra::Texture::TextureFormat::A1B5G5R5;
+ case PixelFormat::R8:
+ return Tegra::Texture::TextureFormat::R8;
+ case PixelFormat::RGBA16F:
+ return Tegra::Texture::TextureFormat::R16_G16_B16_A16;
+ case PixelFormat::R11FG11FB10F:
+ return Tegra::Texture::TextureFormat::BF10GF11RF11;
+ case PixelFormat::DXT1:
+ return Tegra::Texture::TextureFormat::DXT1;
+ case PixelFormat::DXT23:
+ return Tegra::Texture::TextureFormat::DXT23;
+ case PixelFormat::DXT45:
+ return Tegra::Texture::TextureFormat::DXT45;
+ case PixelFormat::DXN1:
+ return Tegra::Texture::TextureFormat::DXN1;
+ default:
+ UNREACHABLE();
+ }
+ }
+
+ static ComponentType ComponentTypeFromTexture(Tegra::Texture::ComponentType type) {
+ // TODO(Subv): Implement more component types
+ switch (type) {
+ case Tegra::Texture::ComponentType::UNORM:
+ return ComponentType::UNorm;
+ default:
+ NGLOG_CRITICAL(HW_GPU, "Unimplemented component type={}", static_cast<u32>(type));
+ UNREACHABLE();
+ }
+ }
+
+ static ComponentType ComponentTypeFromRenderTarget(Tegra::RenderTargetFormat format) {
+ // TODO(Subv): Implement more render targets
+ switch (format) {
+ case Tegra::RenderTargetFormat::RGBA8_UNORM:
+ case Tegra::RenderTargetFormat::RGBA8_SRGB:
+ case Tegra::RenderTargetFormat::RGB10_A2_UNORM:
+ return ComponentType::UNorm;
+ case Tegra::RenderTargetFormat::RGBA16_FLOAT:
+ case Tegra::RenderTargetFormat::R11G11B10_FLOAT:
+ return ComponentType::Float;
+ default:
+ NGLOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format));
+ UNREACHABLE();
+ }
+ }
+
+ static ComponentType ComponentTypeFromGPUPixelFormat(
+ Tegra::FramebufferConfig::PixelFormat format) {
+ switch (format) {
+ case Tegra::FramebufferConfig::PixelFormat::ABGR8:
+ return ComponentType::UNorm;
+ default:
+ NGLOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format));
+ UNREACHABLE();
+ }
+ }
+
+ static bool CheckFormatsBlittable(PixelFormat pixel_format_a, PixelFormat pixel_format_b) {
+ SurfaceType a_type = GetFormatType(pixel_format_a);
+ SurfaceType b_type = GetFormatType(pixel_format_b);
+
+ if (a_type == SurfaceType::ColorTexture && b_type == SurfaceType::ColorTexture) {
+ return true;
+ }
+
+ if (a_type == SurfaceType::Depth && b_type == SurfaceType::Depth) {
+ return true;
+ }
+
+ if (a_type == SurfaceType::DepthStencil && b_type == SurfaceType::DepthStencil) {
+ return true;
+ }
+
+ return false;
+ }
+
+ static SurfaceType GetFormatType(PixelFormat pixel_format) {
+ if (static_cast<size_t>(pixel_format) < MaxPixelFormat) {
+ return SurfaceType::ColorTexture;
+ }
+
+ // TODO(Subv): Implement the other formats
+ ASSERT(false);
+
+ return SurfaceType::Invalid;
+ }
+
+ /// Update the params "size", "end" and "type" from the already set "addr", "width", "height"
+ /// and "pixel_format"
+ void UpdateParams() {
+ if (stride == 0) {
+ stride = width;
+ }
+ type = GetFormatType(pixel_format);
+ size = !is_tiled ? BytesInPixels(stride * (height - 1) + width)
+ : BytesInPixels(stride * 8 * (height / 8 - 1) + width * 8);
+ end = addr + size;
+ }
+
+ SurfaceInterval GetInterval() const {
+ return SurfaceInterval::right_open(addr, end);
+ }
+
+ // Returns the outer rectangle containing "interval"
+ SurfaceParams FromInterval(SurfaceInterval interval) const;
+
+ SurfaceInterval GetSubRectInterval(MathUtil::Rectangle<u32> unscaled_rect) const;
+
+ // Returns the region of the biggest valid rectange within interval
+ SurfaceInterval GetCopyableInterval(const Surface& src_surface) const;
+
+ /**
+ * Gets the actual width (in pixels) of the surface. This is provided because `width` is used
+ * for tracking the surface region in memory, which may be compressed for certain formats. In
+ * this scenario, `width` is actually the compressed width.
+ */
+ u32 GetActualWidth() const {
+ return width * GetCompresssionFactor();
+ }
+
+ /**
+ * Gets the actual height (in pixels) of the surface. This is provided because `height` is used
+ * for tracking the surface region in memory, which may be compressed for certain formats. In
+ * this scenario, `height` is actually the compressed height.
+ */
+ u32 GetActualHeight() const {
+ return height * GetCompresssionFactor();
+ }
+
+ u32 GetScaledWidth() const {
+ return width * res_scale;
+ }
+
+ u32 GetScaledHeight() const {
+ return height * res_scale;
+ }
+
+ MathUtil::Rectangle<u32> GetRect() const {
+ return {0, height, width, 0};
+ }
+
+ MathUtil::Rectangle<u32> GetScaledRect() const {
+ return {0, GetScaledHeight(), GetScaledWidth(), 0};
+ }
+
+ u64 PixelsInBytes(u64 size) const {
+ return size * CHAR_BIT / GetFormatBpp(pixel_format);
+ }
+
+ u64 BytesInPixels(u64 pixels) const {
+ return pixels * GetFormatBpp(pixel_format) / CHAR_BIT;
+ }
+
+ VAddr GetCpuAddr() const;
+
+ bool ExactMatch(const SurfaceParams& other_surface) const;
+ bool CanSubRect(const SurfaceParams& sub_surface) const;
+ bool CanExpand(const SurfaceParams& expanded_surface) const;
+ bool CanTexCopy(const SurfaceParams& texcopy_params) const;
+
+ MathUtil::Rectangle<u32> GetSubRect(const SurfaceParams& sub_surface) const;
+ MathUtil::Rectangle<u32> GetScaledSubRect(const SurfaceParams& sub_surface) const;
+
+ Tegra::GPUVAddr addr = 0;
+ Tegra::GPUVAddr end = 0;
+ boost::optional<VAddr> cpu_addr;
+ u64 size = 0;
+
+ u32 width = 0;
+ u32 height = 0;
+ u32 stride = 0;
+ u32 block_height = 0;
+ u16 res_scale = 1;
+
+ bool is_tiled = false;
+ PixelFormat pixel_format = PixelFormat::Invalid;
+ SurfaceType type = SurfaceType::Invalid;
+ ComponentType component_type = ComponentType::Invalid;
+};
+
+struct CachedSurface : SurfaceParams {
+ bool CanFill(const SurfaceParams& dest_surface, SurfaceInterval fill_interval) const;
+ bool CanCopy(const SurfaceParams& dest_surface, SurfaceInterval copy_interval) const;
+
+ bool IsRegionValid(SurfaceInterval interval) const {
+ return (invalid_regions.find(interval) == invalid_regions.end());
+ }
+
+ bool IsSurfaceFullyInvalid() const {
+ return (invalid_regions & GetInterval()) == SurfaceRegions(GetInterval());
+ }
+
+ bool registered = false;
+ SurfaceRegions invalid_regions;
+
+ u64 fill_size = 0; /// Number of bytes to read from fill_data
+ std::array<u8, 4> fill_data;
+
+ OGLTexture texture;
+
+ static constexpr unsigned int GetGLBytesPerPixel(PixelFormat format) {
+ if (format == PixelFormat::Invalid)
+ return 0;
+
+ return SurfaceParams::GetFormatBpp(format) / CHAR_BIT;
+ }
+
+ std::unique_ptr<u8[]> gl_buffer;
+ size_t gl_buffer_size = 0;
+
+ // Read/Write data in Switch memory to/from gl_buffer
+ void LoadGLBuffer(Tegra::GPUVAddr load_start, Tegra::GPUVAddr load_end);
+ void FlushGLBuffer(Tegra::GPUVAddr flush_start, Tegra::GPUVAddr flush_end);
+
+ // Upload/Download data in gl_buffer in/to this surface's texture
+ void UploadGLTexture(const MathUtil::Rectangle<u32>& rect, GLuint read_fb_handle,
+ GLuint draw_fb_handle);
+ void DownloadGLTexture(const MathUtil::Rectangle<u32>& rect, GLuint read_fb_handle,
+ GLuint draw_fb_handle);
+};
+
+class RasterizerCacheOpenGL : NonCopyable {
+public:
+ RasterizerCacheOpenGL();
+ ~RasterizerCacheOpenGL();
+
+ /// Blit one surface's texture to another
+ bool BlitSurfaces(const Surface& src_surface, const MathUtil::Rectangle<u32>& src_rect,
+ const Surface& dst_surface, const MathUtil::Rectangle<u32>& dst_rect);
+
+ void ConvertD24S8toABGR(GLuint src_tex, const MathUtil::Rectangle<u32>& src_rect,
+ GLuint dst_tex, const MathUtil::Rectangle<u32>& dst_rect);
+
+ /// Copy one surface's region to another
+ void CopySurface(const Surface& src_surface, const Surface& dst_surface,
+ SurfaceInterval copy_interval);
+
+ /// Load a texture from Switch memory to OpenGL and cache it (if not already cached)
+ Surface GetSurface(const SurfaceParams& params, ScaleMatch match_res_scale,
+ bool load_if_create);
+
+ /// Tries to find a framebuffer GPU address based on the provided CPU address
+ boost::optional<Tegra::GPUVAddr> TryFindFramebufferGpuAddress(VAddr cpu_addr) const;
+
+ /// Attempt to find a subrect (resolution scaled) of a surface, otherwise loads a texture from
+ /// Switch memory to OpenGL and caches it (if not already cached)
+ SurfaceRect_Tuple GetSurfaceSubRect(const SurfaceParams& params, ScaleMatch match_res_scale,
+ bool load_if_create);
+
+ /// Get a surface based on the texture configuration
+ Surface GetTextureSurface(const Tegra::Texture::FullTextureInfo& config);
+
+ /// Get the color and depth surfaces based on the framebuffer configuration
+ SurfaceSurfaceRect_Tuple GetFramebufferSurfaces(bool using_color_fb, bool using_depth_fb,
+ const MathUtil::Rectangle<s32>& viewport);
+
+ /// Get a surface that matches the fill config
+ Surface GetFillSurface(const void* config);
+
+ /// Get a surface that matches a "texture copy" display transfer config
+ SurfaceRect_Tuple GetTexCopySurface(const SurfaceParams& params);
+
+ /// Write any cached resources overlapping the region back to memory (if dirty)
+ void FlushRegion(Tegra::GPUVAddr addr, u64 size, Surface flush_surface = nullptr);
+
+ /// Mark region as being invalidated by region_owner (nullptr if Switch memory)
+ void InvalidateRegion(Tegra::GPUVAddr addr, u64 size, const Surface& region_owner);
+
+ /// Flush all cached resources tracked by this cache manager
+ void FlushAll();
+
+private:
+ void DuplicateSurface(const Surface& src_surface, const Surface& dest_surface);
+
+ /// Update surface's texture for given region when necessary
+ void ValidateSurface(const Surface& surface, Tegra::GPUVAddr addr, u64 size);
+
+ /// Create a new surface
+ Surface CreateSurface(const SurfaceParams& params);
+
+ /// Register surface into the cache
+ void RegisterSurface(const Surface& surface);
+
+ /// Remove surface from the cache
+ void UnregisterSurface(const Surface& surface);
+
+ /// Increase/decrease the number of surface in pages touching the specified region
+ void UpdatePagesCachedCount(Tegra::GPUVAddr addr, u64 size, int delta);
+
+ SurfaceCache surface_cache;
+ PageMap cached_pages;
+ SurfaceMap dirty_regions;
+ SurfaceSet remove_surfaces;
+
+ OGLFramebuffer read_framebuffer;
+ OGLFramebuffer draw_framebuffer;
+
+ OGLVertexArray attributeless_vao;
+ OGLBuffer d24s8_abgr_buffer;
+ GLsizeiptr d24s8_abgr_buffer_size;
+ OGLProgram d24s8_abgr_shader;
+ GLint d24s8_abgr_tbo_size_u_id;
+ GLint d24s8_abgr_viewport_u_id;
+};
diff --git a/src/video_core/renderer_opengl/gl_resource_manager.h b/src/video_core/renderer_opengl/gl_resource_manager.h
index 13301ec9f..93f9172e7 100644
--- a/src/video_core/renderer_opengl/gl_resource_manager.h
+++ b/src/video_core/renderer_opengl/gl_resource_manager.h
@@ -13,14 +13,16 @@
class OGLTexture : private NonCopyable {
public:
OGLTexture() = default;
- OGLTexture(OGLTexture&& o) {
- std::swap(handle, o.handle);
- }
+
+ OGLTexture(OGLTexture&& o) noexcept : handle(std::exchange(o.handle, 0)) {}
+
~OGLTexture() {
Release();
}
- OGLTexture& operator=(OGLTexture&& o) {
- std::swap(handle, o.handle);
+
+ OGLTexture& operator=(OGLTexture&& o) noexcept {
+ Release();
+ handle = std::exchange(o.handle, 0);
return *this;
}
@@ -36,7 +38,7 @@ public:
if (handle == 0)
return;
glDeleteTextures(1, &handle);
- OpenGLState::ResetTexture(handle);
+ OpenGLState::GetCurState().ResetTexture(handle).Apply();
handle = 0;
}
@@ -46,14 +48,16 @@ public:
class OGLSampler : private NonCopyable {
public:
OGLSampler() = default;
- OGLSampler(OGLSampler&& o) {
- std::swap(handle, o.handle);
- }
+
+ OGLSampler(OGLSampler&& o) noexcept : handle(std::exchange(o.handle, 0)) {}
+
~OGLSampler() {
Release();
}
- OGLSampler& operator=(OGLSampler&& o) {
- std::swap(handle, o.handle);
+
+ OGLSampler& operator=(OGLSampler&& o) noexcept {
+ Release();
+ handle = std::exchange(o.handle, 0);
return *this;
}
@@ -69,7 +73,7 @@ public:
if (handle == 0)
return;
glDeleteSamplers(1, &handle);
- OpenGLState::ResetSampler(handle);
+ OpenGLState::GetCurState().ResetSampler(handle).Apply();
handle = 0;
}
@@ -79,22 +83,71 @@ public:
class OGLShader : private NonCopyable {
public:
OGLShader() = default;
- OGLShader(OGLShader&& o) {
- std::swap(handle, o.handle);
- }
+
+ OGLShader(OGLShader&& o) noexcept : handle(std::exchange(o.handle, 0)) {}
+
~OGLShader() {
Release();
}
- OGLShader& operator=(OGLShader&& o) {
- std::swap(handle, o.handle);
+
+ OGLShader& operator=(OGLShader&& o) noexcept {
+ Release();
+ handle = std::exchange(o.handle, 0);
return *this;
}
- /// Creates a new internal OpenGL resource and stores the handle
- void Create(const char* vert_shader, const char* frag_shader) {
+ void Create(const char* source, GLenum type) {
if (handle != 0)
return;
- handle = GLShader::LoadProgram(vert_shader, frag_shader);
+ if (source == nullptr)
+ return;
+ handle = GLShader::LoadShader(source, type);
+ }
+
+ void Release() {
+ if (handle == 0)
+ return;
+ glDeleteShader(handle);
+ handle = 0;
+ }
+
+ GLuint handle = 0;
+};
+
+class OGLProgram : private NonCopyable {
+public:
+ OGLProgram() = default;
+
+ OGLProgram(OGLProgram&& o) noexcept : handle(std::exchange(o.handle, 0)) {}
+
+ ~OGLProgram() {
+ Release();
+ }
+
+ OGLProgram& operator=(OGLProgram&& o) noexcept {
+ Release();
+ handle = std::exchange(o.handle, 0);
+ return *this;
+ }
+
+ template <typename... T>
+ void Create(bool separable_program, T... shaders) {
+ if (handle != 0)
+ return;
+ handle = GLShader::LoadProgram(separable_program, shaders...);
+ }
+
+ /// Creates a new internal OpenGL resource and stores the handle
+ void CreateFromSource(const char* vert_shader, const char* geo_shader, const char* frag_shader,
+ bool separable_program = false) {
+ OGLShader vert, geo, frag;
+ if (vert_shader)
+ vert.Create(vert_shader, GL_VERTEX_SHADER);
+ if (geo_shader)
+ geo.Create(geo_shader, GL_GEOMETRY_SHADER);
+ if (frag_shader)
+ frag.Create(frag_shader, GL_FRAGMENT_SHADER);
+ Create(separable_program, vert.handle, geo.handle, frag.handle);
}
/// Deletes the internal OpenGL resource
@@ -102,7 +155,39 @@ public:
if (handle == 0)
return;
glDeleteProgram(handle);
- OpenGLState::ResetProgram(handle);
+ OpenGLState::GetCurState().ResetProgram(handle).Apply();
+ handle = 0;
+ }
+
+ GLuint handle = 0;
+};
+
+class OGLPipeline : private NonCopyable {
+public:
+ OGLPipeline() = default;
+ OGLPipeline(OGLPipeline&& o) noexcept : handle{std::exchange<GLuint>(o.handle, 0)} {}
+
+ ~OGLPipeline() {
+ Release();
+ }
+ OGLPipeline& operator=(OGLPipeline&& o) noexcept {
+ handle = std::exchange<GLuint>(o.handle, 0);
+ return *this;
+ }
+
+ /// Creates a new internal OpenGL resource and stores the handle
+ void Create() {
+ if (handle != 0)
+ return;
+ glGenProgramPipelines(1, &handle);
+ }
+
+ /// Deletes the internal OpenGL resource
+ void Release() {
+ if (handle == 0)
+ return;
+ glDeleteProgramPipelines(1, &handle);
+ OpenGLState::GetCurState().ResetPipeline(handle).Apply();
handle = 0;
}
@@ -112,14 +197,16 @@ public:
class OGLBuffer : private NonCopyable {
public:
OGLBuffer() = default;
- OGLBuffer(OGLBuffer&& o) {
- std::swap(handle, o.handle);
- }
+
+ OGLBuffer(OGLBuffer&& o) noexcept : handle(std::exchange(o.handle, 0)) {}
+
~OGLBuffer() {
Release();
}
- OGLBuffer& operator=(OGLBuffer&& o) {
- std::swap(handle, o.handle);
+
+ OGLBuffer& operator=(OGLBuffer&& o) noexcept {
+ Release();
+ handle = std::exchange(o.handle, 0);
return *this;
}
@@ -135,24 +222,59 @@ public:
if (handle == 0)
return;
glDeleteBuffers(1, &handle);
- OpenGLState::ResetBuffer(handle);
+ OpenGLState::GetCurState().ResetBuffer(handle).Apply();
handle = 0;
}
GLuint handle = 0;
};
+class OGLSync : private NonCopyable {
+public:
+ OGLSync() = default;
+
+ OGLSync(OGLSync&& o) noexcept : handle(std::exchange(o.handle, nullptr)) {}
+
+ ~OGLSync() {
+ Release();
+ }
+ OGLSync& operator=(OGLSync&& o) noexcept {
+ Release();
+ handle = std::exchange(o.handle, nullptr);
+ return *this;
+ }
+
+ /// Creates a new internal OpenGL resource and stores the handle
+ void Create() {
+ if (handle != 0)
+ return;
+ handle = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
+ }
+
+ /// Deletes the internal OpenGL resource
+ void Release() {
+ if (handle == 0)
+ return;
+ glDeleteSync(handle);
+ handle = 0;
+ }
+
+ GLsync handle = 0;
+};
+
class OGLVertexArray : private NonCopyable {
public:
OGLVertexArray() = default;
- OGLVertexArray(OGLVertexArray&& o) {
- std::swap(handle, o.handle);
- }
+
+ OGLVertexArray(OGLVertexArray&& o) noexcept : handle(std::exchange(o.handle, 0)) {}
+
~OGLVertexArray() {
Release();
}
- OGLVertexArray& operator=(OGLVertexArray&& o) {
- std::swap(handle, o.handle);
+
+ OGLVertexArray& operator=(OGLVertexArray&& o) noexcept {
+ Release();
+ handle = std::exchange(o.handle, 0);
return *this;
}
@@ -168,7 +290,7 @@ public:
if (handle == 0)
return;
glDeleteVertexArrays(1, &handle);
- OpenGLState::ResetVertexArray(handle);
+ OpenGLState::GetCurState().ResetVertexArray(handle).Apply();
handle = 0;
}
@@ -178,14 +300,16 @@ public:
class OGLFramebuffer : private NonCopyable {
public:
OGLFramebuffer() = default;
- OGLFramebuffer(OGLFramebuffer&& o) {
- std::swap(handle, o.handle);
- }
+
+ OGLFramebuffer(OGLFramebuffer&& o) noexcept : handle(std::exchange(o.handle, 0)) {}
+
~OGLFramebuffer() {
Release();
}
- OGLFramebuffer& operator=(OGLFramebuffer&& o) {
- std::swap(handle, o.handle);
+
+ OGLFramebuffer& operator=(OGLFramebuffer&& o) noexcept {
+ Release();
+ handle = std::exchange(o.handle, 0);
return *this;
}
@@ -201,7 +325,7 @@ public:
if (handle == 0)
return;
glDeleteFramebuffers(1, &handle);
- OpenGLState::ResetFramebuffer(handle);
+ OpenGLState::GetCurState().ResetFramebuffer(handle).Apply();
handle = 0;
}
diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
new file mode 100644
index 000000000..6ec0a0742
--- /dev/null
+++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
@@ -0,0 +1,1722 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <map>
+#include <set>
+#include <string>
+#include <string_view>
+#include "common/assert.h"
+#include "common/common_types.h"
+#include "video_core/engines/shader_bytecode.h"
+#include "video_core/renderer_opengl/gl_shader_decompiler.h"
+
+namespace GLShader {
+namespace Decompiler {
+
+using Tegra::Shader::Attribute;
+using Tegra::Shader::Instruction;
+using Tegra::Shader::LogicOperation;
+using Tegra::Shader::OpCode;
+using Tegra::Shader::Register;
+using Tegra::Shader::Sampler;
+using Tegra::Shader::SubOp;
+
+constexpr u32 PROGRAM_END = MAX_PROGRAM_CODE_LENGTH;
+
+class DecompileFail : public std::runtime_error {
+public:
+ using std::runtime_error::runtime_error;
+};
+
+/// 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.
+ AlwaysReturn, ///< All code paths reach the return point.
+ Conditional, ///< Code path reaches the return point or an END instruction conditionally.
+ AlwaysEnd, ///< All code paths reach a END instruction.
+};
+
+/// A subroutine is a range of code refereced by a CALL, IF or LOOP instruction.
+struct Subroutine {
+ /// Generates a name suitable for GLSL source code.
+ std::string GetName() const {
+ return "sub_" + std::to_string(begin) + '_' + std::to_string(end);
+ }
+
+ u32 begin; ///< Entry point of the subroutine.
+ u32 end; ///< Return point of the subroutine.
+ ExitMethod exit_method; ///< Exit method of the subroutine.
+ std::set<u32> labels; ///< Addresses refereced by JMP instructions.
+
+ bool operator<(const Subroutine& rhs) const {
+ return std::tie(begin, end) < std::tie(rhs.begin, rhs.end);
+ }
+};
+
+/// Analyzes shader code and produces a set of subroutines.
+class ControlFlowAnalyzer {
+public:
+ ControlFlowAnalyzer(const ProgramCode& program_code, u32 main_offset)
+ : program_code(program_code) {
+
+ // Recursively finds all subroutines.
+ const Subroutine& program_main = AddSubroutine(main_offset, PROGRAM_END);
+ if (program_main.exit_method != ExitMethod::AlwaysEnd)
+ throw DecompileFail("Program does not always end");
+ }
+
+ std::set<Subroutine> GetSubroutines() {
+ return std::move(subroutines);
+ }
+
+private:
+ const ProgramCode& program_code;
+ std::set<Subroutine> subroutines;
+ std::map<std::pair<u32, u32>, ExitMethod> exit_method_map;
+
+ /// Adds and analyzes a new subroutine if it is not added yet.
+ const Subroutine& AddSubroutine(u32 begin, u32 end) {
+ auto iter = subroutines.find(Subroutine{begin, end});
+ if (iter != subroutines.end())
+ return *iter;
+
+ Subroutine subroutine{begin, end};
+ subroutine.exit_method = Scan(begin, end, subroutine.labels);
+ if (subroutine.exit_method == ExitMethod::Undetermined)
+ throw DecompileFail("Recursive function detected");
+ return *subroutines.insert(std::move(subroutine)).first;
+ }
+
+ /// Merges exit method of two parallel branches.
+ static ExitMethod ParallelExit(ExitMethod a, ExitMethod b) {
+ if (a == ExitMethod::Undetermined) {
+ return b;
+ }
+ if (b == ExitMethod::Undetermined) {
+ return a;
+ }
+ if (a == b) {
+ return a;
+ }
+ return ExitMethod::Conditional;
+ }
+
+ /// Scans a range of code for labels and determines the exit method.
+ ExitMethod Scan(u32 begin, u32 end, std::set<u32>& labels) {
+ auto [iter, inserted] =
+ exit_method_map.emplace(std::make_pair(begin, end), ExitMethod::Undetermined);
+ ExitMethod& exit_method = iter->second;
+ if (!inserted)
+ return exit_method;
+
+ for (u32 offset = begin; offset != end && offset != PROGRAM_END; ++offset) {
+ const Instruction instr = {program_code[offset]};
+ if (const auto opcode = OpCode::Decode(instr)) {
+ switch (opcode->GetId()) {
+ case OpCode::Id::EXIT: {
+ // The EXIT instruction can be predicated, which means that the shader can
+ // conditionally end on this instruction. We have to consider the case where the
+ // condition is not met and check the exit method of that other basic block.
+ using Tegra::Shader::Pred;
+ if (instr.pred.pred_index == static_cast<u64>(Pred::UnusedIndex)) {
+ return exit_method = ExitMethod::AlwaysEnd;
+ } else {
+ ExitMethod not_met = Scan(offset + 1, end, labels);
+ return exit_method = ParallelExit(ExitMethod::AlwaysEnd, not_met);
+ }
+ }
+ case OpCode::Id::BRA: {
+ u32 target = offset + instr.bra.GetBranchTarget();
+ labels.insert(target);
+ ExitMethod no_jmp = Scan(offset + 1, end, labels);
+ ExitMethod jmp = Scan(target, end, labels);
+ return exit_method = ParallelExit(no_jmp, jmp);
+ }
+ }
+ }
+ }
+ return exit_method = ExitMethod::AlwaysReturn;
+ }
+};
+
+class ShaderWriter {
+public:
+ void AddLine(std::string_view text) {
+ DEBUG_ASSERT(scope >= 0);
+ if (!text.empty()) {
+ AppendIndentation();
+ }
+ shader_source += text;
+ AddNewLine();
+ }
+
+ void AddLine(char character) {
+ DEBUG_ASSERT(scope >= 0);
+ AppendIndentation();
+ shader_source += character;
+ AddNewLine();
+ }
+
+ void AddNewLine() {
+ DEBUG_ASSERT(scope >= 0);
+ shader_source += '\n';
+ }
+
+ std::string GetResult() {
+ return std::move(shader_source);
+ }
+
+ int scope = 0;
+
+private:
+ void AppendIndentation() {
+ shader_source.append(static_cast<size_t>(scope) * 4, ' ');
+ }
+
+ std::string shader_source;
+};
+
+/**
+ * Represents an emulated shader register, used to track the state of that register for emulation
+ * with GLSL. At this time, a register can be used as a float or an integer. This class is used for
+ * bookkeeping within the GLSL program.
+ */
+class GLSLRegister {
+public:
+ enum class Type {
+ Float,
+ Integer,
+ UnsignedInteger,
+ };
+
+ GLSLRegister(size_t index, ShaderWriter& shader) : index{index}, shader{shader} {}
+
+ /// Gets the GLSL type string for a register
+ static std::string GetTypeString(Type type) {
+ switch (type) {
+ case Type::Float:
+ return "float";
+ case Type::Integer:
+ return "int";
+ case Type::UnsignedInteger:
+ return "uint";
+ }
+
+ UNREACHABLE();
+ return {};
+ }
+
+ /// Gets the GLSL register prefix string, used for declarations and referencing
+ static std::string GetPrefixString(Type type) {
+ return "reg_" + GetTypeString(type) + '_';
+ }
+
+ /// Returns a GLSL string representing the current state of the register
+ const std::string GetActiveString() {
+ declr_type.insert(active_type);
+ return GetPrefixString(active_type) + std::to_string(index);
+ }
+
+ /// Returns true if the active type is a float
+ bool IsFloat() const {
+ return active_type == Type::Float;
+ }
+
+ /// Returns true if the active type is an integer
+ bool IsInteger() const {
+ return active_type == Type::Integer;
+ }
+
+ /// Returns the current active type of the register
+ Type GetActiveType() const {
+ return active_type;
+ }
+
+ /// Returns the index of the register
+ size_t GetIndex() const {
+ return index;
+ }
+
+ /// Returns a set of the declared types of the register
+ const std::set<Type>& DeclaredTypes() const {
+ return declr_type;
+ }
+
+private:
+ const size_t index;
+ const std::string float_str;
+ const std::string integer_str;
+ ShaderWriter& shader;
+ Type active_type{Type::Float};
+ std::set<Type> declr_type;
+};
+
+/**
+ * Used to manage shader registers that are emulated with GLSL. This class keeps track of the state
+ * of all registers (e.g. whether they are currently being used as Floats or Integers), and
+ * generates the necessary GLSL code to perform conversions as needed. This class is used for
+ * bookkeeping within the GLSL program.
+ */
+class GLSLRegisterManager {
+public:
+ GLSLRegisterManager(ShaderWriter& shader, ShaderWriter& declarations,
+ const Maxwell3D::Regs::ShaderStage& stage)
+ : shader{shader}, declarations{declarations}, stage{stage} {
+ BuildRegisterList();
+ }
+
+ /**
+ * Returns code that does an integer size conversion for the specified size.
+ * @param value Value to perform integer size conversion on.
+ * @param size Register size to use for conversion instructions.
+ * @returns GLSL string corresponding to the value converted to the specified size.
+ */
+ static std::string ConvertIntegerSize(const std::string& value, Register::Size size) {
+ switch (size) {
+ case Register::Size::Byte:
+ return "((" + value + " << 24) >> 24)";
+ case Register::Size::Short:
+ return "((" + value + " << 16) >> 16)";
+ case Register::Size::Word:
+ // Default - do nothing
+ return value;
+ default:
+ NGLOG_CRITICAL(HW_GPU, "Unimplemented conversion size {}", static_cast<u32>(size));
+ UNREACHABLE();
+ }
+ }
+
+ /**
+ * Gets a register as an float.
+ * @param reg The register to get.
+ * @param elem The element to use for the operation.
+ * @returns GLSL string corresponding to the register as a float.
+ */
+ std::string GetRegisterAsFloat(const Register& reg, unsigned elem = 0) {
+ ASSERT(regs[reg].IsFloat());
+ return GetRegister(reg, elem);
+ }
+
+ /**
+ * Gets a register as an integer.
+ * @param reg The register to get.
+ * @param elem The element to use for the operation.
+ * @param is_signed Whether to get the register as a signed (or unsigned) integer.
+ * @param size Register size to use for conversion instructions.
+ * @returns GLSL string corresponding to the register as an integer.
+ */
+ std::string GetRegisterAsInteger(const Register& reg, unsigned elem = 0, bool is_signed = true,
+ Register::Size size = Register::Size::Word) {
+ const std::string func = GetGLSLConversionFunc(
+ GLSLRegister::Type::Float,
+ is_signed ? GLSLRegister::Type::Integer : GLSLRegister::Type::UnsignedInteger);
+
+ std::string value = func + '(' + GetRegister(reg, elem) + ')';
+
+ return ConvertIntegerSize(value, size);
+ }
+
+ /**
+ * Writes code that does a register assignment to float value operation.
+ * @param reg The destination register to use.
+ * @param elem The element to use for the operation.
+ * @param value The code representing the value to assign.
+ * @param dest_num_components Number of components in the destination.
+ * @param value_num_components Number of components in the value.
+ * @param is_saturated Optional, when True, saturates the provided value.
+ * @param dest_elem Optional, the destination element to use for the operation.
+ */
+ void SetRegisterToFloat(const Register& reg, u64 elem, const std::string& value,
+ u64 dest_num_components, u64 value_num_components,
+ bool is_saturated = false, u64 dest_elem = 0) {
+
+ SetRegister(reg, elem, is_saturated ? "clamp(" + value + ", 0.0, 1.0)" : value,
+ dest_num_components, value_num_components, dest_elem);
+ }
+
+ /**
+ * Writes code that does a register assignment to integer value operation.
+ * @param reg The destination register to use.
+ * @param elem The element to use for the operation.
+ * @param value The code representing the value to assign.
+ * @param dest_num_components Number of components in the destination.
+ * @param value_num_components Number of components in the value.
+ * @param is_saturated Optional, when True, saturates the provided value.
+ * @param dest_elem Optional, the destination element to use for the operation.
+ * @param size Register size to use for conversion instructions.
+ */
+ void SetRegisterToInteger(const Register& reg, bool is_signed, u64 elem,
+ const std::string& value, u64 dest_num_components,
+ u64 value_num_components, bool is_saturated = false,
+ u64 dest_elem = 0, Register::Size size = Register::Size::Word) {
+ ASSERT_MSG(!is_saturated, "Unimplemented");
+
+ const std::string func = GetGLSLConversionFunc(
+ is_signed ? GLSLRegister::Type::Integer : GLSLRegister::Type::UnsignedInteger,
+ GLSLRegister::Type::Float);
+
+ SetRegister(reg, elem, func + '(' + ConvertIntegerSize(value, size) + ')',
+ dest_num_components, value_num_components, dest_elem);
+ }
+
+ /**
+ * Writes code that does a register assignment to input attribute operation. Input attributes
+ * are stored as floats, so this may require conversion.
+ * @param reg The destination register to use.
+ * @param elem The element to use for the operation.
+ * @param attribute The input attribute to use as the source value.
+ */
+ void SetRegisterToInputAttibute(const Register& reg, u64 elem, Attribute::Index attribute) {
+ std::string dest = GetRegisterAsFloat(reg);
+ std::string src = GetInputAttribute(attribute) + GetSwizzle(elem);
+
+ if (regs[reg].IsFloat()) {
+ shader.AddLine(dest + " = " + src + ';');
+ } else if (regs[reg].IsInteger()) {
+ shader.AddLine(dest + " = floatBitsToInt(" + src + ");");
+ } else {
+ UNREACHABLE();
+ }
+ }
+
+ /**
+ * Writes code that does a output attribute assignment to register operation. Output attributes
+ * are stored as floats, so this may require conversion.
+ * @param attribute The destination output attribute.
+ * @param elem The element to use for the operation.
+ * @param reg The register to use as the source value.
+ */
+ void SetOutputAttributeToRegister(Attribute::Index attribute, u64 elem, const Register& reg) {
+ std::string dest = GetOutputAttribute(attribute) + GetSwizzle(elem);
+ std::string src = GetRegisterAsFloat(reg);
+ ASSERT_MSG(regs[reg].IsFloat(), "Output attributes must be set to a float");
+ shader.AddLine(dest + " = " + src + ';');
+ }
+
+ /// Generates code representing a uniform (C buffer) register, interpreted as the input type.
+ std::string GetUniform(u64 index, u64 offset, GLSLRegister::Type type) {
+ declr_const_buffers[index].MarkAsUsed(index, offset, stage);
+ std::string value = 'c' + std::to_string(index) + '[' + std::to_string(offset) + ']';
+
+ if (type == GLSLRegister::Type::Float) {
+ return value;
+ } else if (type == GLSLRegister::Type::Integer) {
+ return "floatBitsToInt(" + value + ')';
+ } else {
+ UNREACHABLE();
+ }
+ }
+
+ std::string GetUniformIndirect(u64 index, s64 offset, const Register& index_reg,
+ GLSLRegister::Type type) {
+ declr_const_buffers[index].MarkAsUsedIndirect(index, stage);
+ std::string value = 'c' + std::to_string(index) + "[(floatBitsToInt(" +
+ GetRegister(index_reg, 0) + ") + " + std::to_string(offset) + ") / 4]";
+
+ if (type == GLSLRegister::Type::Float) {
+ return value;
+ } else if (type == GLSLRegister::Type::Integer) {
+ return "floatBitsToInt(" + value + ')';
+ } else {
+ UNREACHABLE();
+ }
+ }
+
+ /// Add declarations for registers
+ void GenerateDeclarations() {
+ for (const auto& reg : regs) {
+ for (const auto& type : reg.DeclaredTypes()) {
+ declarations.AddLine(GLSLRegister::GetTypeString(type) + ' ' +
+ GLSLRegister::GetPrefixString(type) +
+ std::to_string(reg.GetIndex()) + " = 0;");
+ }
+ }
+ declarations.AddNewLine();
+
+ for (const auto& index : declr_input_attribute) {
+ // TODO(bunnei): Use proper number of elements for these
+ declarations.AddLine("layout(location = " +
+ std::to_string(static_cast<u32>(index) -
+ static_cast<u32>(Attribute::Index::Attribute_0)) +
+ ") in vec4 " + GetInputAttribute(index) + ';');
+ }
+ declarations.AddNewLine();
+
+ for (const auto& index : declr_output_attribute) {
+ // TODO(bunnei): Use proper number of elements for these
+ declarations.AddLine("layout(location = " +
+ std::to_string(static_cast<u32>(index) -
+ static_cast<u32>(Attribute::Index::Attribute_0)) +
+ ") out vec4 " + GetOutputAttribute(index) + ';');
+ }
+ declarations.AddNewLine();
+
+ unsigned const_buffer_layout = 0;
+ for (const auto& entry : GetConstBuffersDeclarations()) {
+ declarations.AddLine("layout(std430) buffer " + entry.GetName());
+ declarations.AddLine('{');
+ declarations.AddLine(" float c" + std::to_string(entry.GetIndex()) + "[];");
+ declarations.AddLine("};");
+ declarations.AddNewLine();
+ ++const_buffer_layout;
+ }
+ declarations.AddNewLine();
+
+ // Append the sampler2D array for the used textures.
+ size_t num_samplers = GetSamplers().size();
+ if (num_samplers > 0) {
+ declarations.AddLine("uniform sampler2D " + SamplerEntry::GetArrayName(stage) + '[' +
+ std::to_string(num_samplers) + "];");
+ declarations.AddNewLine();
+ }
+ }
+
+ /// Returns a list of constant buffer declarations
+ std::vector<ConstBufferEntry> GetConstBuffersDeclarations() const {
+ std::vector<ConstBufferEntry> result;
+ std::copy_if(declr_const_buffers.begin(), declr_const_buffers.end(),
+ std::back_inserter(result), [](const auto& entry) { return entry.IsUsed(); });
+ return result;
+ }
+
+ /// Returns a list of samplers used in the shader
+ std::vector<SamplerEntry> GetSamplers() const {
+ return used_samplers;
+ }
+
+ /// Returns the GLSL sampler used for the input shader sampler, and creates a new one if
+ /// necessary.
+ std::string AccessSampler(const Sampler& sampler) {
+ size_t offset = static_cast<size_t>(sampler.index.Value());
+
+ // If this sampler has already been used, return the existing mapping.
+ auto itr =
+ std::find_if(used_samplers.begin(), used_samplers.end(),
+ [&](const SamplerEntry& entry) { return entry.GetOffset() == offset; });
+
+ if (itr != used_samplers.end()) {
+ return itr->GetName();
+ }
+
+ // Otherwise create a new mapping for this sampler
+ size_t next_index = used_samplers.size();
+ SamplerEntry entry{stage, offset, next_index};
+ used_samplers.emplace_back(entry);
+ return entry.GetName();
+ }
+
+private:
+ /// Build GLSL conversion function, e.g. floatBitsToInt, intBitsToFloat, etc.
+ const std::string GetGLSLConversionFunc(GLSLRegister::Type src, GLSLRegister::Type dest) const {
+ const std::string src_type = GLSLRegister::GetTypeString(src);
+ std::string dest_type = GLSLRegister::GetTypeString(dest);
+ dest_type[0] = toupper(dest_type[0]);
+ return src_type + "BitsTo" + dest_type;
+ }
+
+ /// Generates code representing a temporary (GPR) register.
+ std::string GetRegister(const Register& reg, unsigned elem) {
+ if (reg == Register::ZeroIndex) {
+ return "0";
+ }
+
+ return regs[reg.GetSwizzledIndex(elem)].GetActiveString();
+ }
+
+ /**
+ * Writes code that does a register assignment to value operation.
+ * @param reg The destination register to use.
+ * @param elem The element to use for the operation.
+ * @param value The code representing the value to assign.
+ * @param dest_num_components Number of components in the destination.
+ * @param value_num_components Number of components in the value.
+ * @param dest_elem Optional, the destination element to use for the operation.
+ */
+ void SetRegister(const Register& reg, u64 elem, const std::string& value,
+ u64 dest_num_components, u64 value_num_components, u64 dest_elem) {
+ std::string dest = GetRegister(reg, dest_elem);
+ if (dest_num_components > 1) {
+ dest += GetSwizzle(elem);
+ }
+
+ std::string src = '(' + value + ')';
+ if (value_num_components > 1) {
+ src += GetSwizzle(elem);
+ }
+
+ shader.AddLine(dest + " = " + src + ';');
+ }
+
+ /// Build the GLSL register list.
+ void BuildRegisterList() {
+ for (size_t index = 0; index < Register::NumRegisters; ++index) {
+ regs.emplace_back(index, shader);
+ }
+ }
+
+ /// Generates code representing an input attribute register.
+ std::string GetInputAttribute(Attribute::Index attribute) {
+ switch (attribute) {
+ case Attribute::Index::Position:
+ return "position";
+ 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 == Maxwell3D::Regs::ShaderStage::Vertex);
+ return "vec4(0, 0, uintBitsToFloat(gl_InstanceID), uintBitsToFloat(gl_VertexID))";
+ default:
+ const u32 index{static_cast<u32>(attribute) -
+ static_cast<u32>(Attribute::Index::Attribute_0)};
+ if (attribute >= Attribute::Index::Attribute_0) {
+ declr_input_attribute.insert(attribute);
+ return "input_attribute_" + std::to_string(index);
+ }
+
+ NGLOG_CRITICAL(HW_GPU, "Unhandled input attribute: {}", index);
+ UNREACHABLE();
+ }
+ }
+
+ /// Generates code representing an output attribute register.
+ std::string GetOutputAttribute(Attribute::Index attribute) {
+ switch (attribute) {
+ case Attribute::Index::Position:
+ return "position";
+ default:
+ const u32 index{static_cast<u32>(attribute) -
+ static_cast<u32>(Attribute::Index::Attribute_0)};
+ if (attribute >= Attribute::Index::Attribute_0) {
+ declr_output_attribute.insert(attribute);
+ return "output_attribute_" + std::to_string(index);
+ }
+
+ NGLOG_CRITICAL(HW_GPU, "Unhandled output attribute: {}", index);
+ UNREACHABLE();
+ }
+ }
+
+ /// Generates code to use for a swizzle operation.
+ static std::string GetSwizzle(u64 elem) {
+ ASSERT(elem <= 3);
+ std::string swizzle = ".";
+ swizzle += "xyzw"[elem];
+ return swizzle;
+ }
+
+ ShaderWriter& shader;
+ ShaderWriter& declarations;
+ std::vector<GLSLRegister> regs;
+ std::set<Attribute::Index> declr_input_attribute;
+ std::set<Attribute::Index> declr_output_attribute;
+ std::array<ConstBufferEntry, Maxwell3D::Regs::MaxConstBuffers> declr_const_buffers;
+ std::vector<SamplerEntry> used_samplers;
+ const Maxwell3D::Regs::ShaderStage& stage;
+};
+
+class GLSLGenerator {
+public:
+ GLSLGenerator(const std::set<Subroutine>& subroutines, const ProgramCode& program_code,
+ u32 main_offset, Maxwell3D::Regs::ShaderStage stage)
+ : subroutines(subroutines), program_code(program_code), main_offset(main_offset),
+ stage(stage) {
+
+ Generate();
+ }
+
+ std::string GetShaderCode() {
+ return declarations.GetResult() + shader.GetResult();
+ }
+
+ /// Returns entries in the shader that are useful for external functions
+ ShaderEntries GetEntries() const {
+ return {regs.GetConstBuffersDeclarations(), regs.GetSamplers()};
+ }
+
+private:
+ /// Gets the Subroutine object corresponding to the specified address.
+ const Subroutine& GetSubroutine(u32 begin, u32 end) const {
+ auto iter = subroutines.find(Subroutine{begin, end});
+ ASSERT(iter != subroutines.end());
+ return *iter;
+ }
+
+ /// Generates code representing a 19-bit immediate value
+ static std::string GetImmediate19(const Instruction& instr) {
+ return std::to_string(instr.alu.GetImm20_19());
+ }
+
+ /// Generates code representing a 32-bit immediate value
+ static std::string GetImmediate32(const Instruction& instr) {
+ return std::to_string(instr.alu.GetImm20_32());
+ }
+
+ /// Generates code representing a texture sampler.
+ std::string GetSampler(const Sampler& sampler) {
+ return regs.AccessSampler(sampler);
+ }
+
+ /**
+ * Adds code that calls a subroutine.
+ * @param subroutine the subroutine to call.
+ */
+ void CallSubroutine(const Subroutine& subroutine) {
+ if (subroutine.exit_method == ExitMethod::AlwaysEnd) {
+ shader.AddLine(subroutine.GetName() + "();");
+ shader.AddLine("return true;");
+ } else if (subroutine.exit_method == ExitMethod::Conditional) {
+ shader.AddLine("if (" + subroutine.GetName() + "()) { return true; }");
+ } else {
+ shader.AddLine(subroutine.GetName() + "();");
+ }
+ }
+
+ /*
+ * Writes code that assigns a predicate boolean variable.
+ * @param pred The id of the predicate to write to.
+ * @param value The expression value to assign to the predicate.
+ */
+ void SetPredicate(u64 pred, const std::string& value) {
+ using Tegra::Shader::Pred;
+ // Can't assign to the constant predicate.
+ ASSERT(pred != static_cast<u64>(Pred::UnusedIndex));
+
+ std::string variable = 'p' + std::to_string(pred);
+ shader.AddLine(variable + " = " + value + ';');
+ declr_predicates.insert(std::move(variable));
+ }
+
+ /*
+ * Returns the condition to use in the 'if' for a predicated instruction.
+ * @param instr Instruction to generate the if condition for.
+ * @returns string containing the predicate condition.
+ */
+ std::string GetPredicateCondition(u64 index, bool negate) const {
+ using Tegra::Shader::Pred;
+ std::string variable;
+
+ // Index 7 is used as an 'Always True' condition.
+ if (index == static_cast<u64>(Pred::UnusedIndex))
+ variable = "true";
+ else
+ variable = 'p' + std::to_string(index);
+
+ if (negate) {
+ return "!(" + variable + ')';
+ }
+
+ return variable;
+ }
+
+ /**
+ * Returns the comparison string to use to compare two values in the 'set' family of
+ * instructions.
+ * @params condition The condition used in the 'set'-family instruction.
+ * @returns String corresponding to the GLSL operator that matches the desired comparison.
+ */
+ std::string GetPredicateComparison(Tegra::Shader::PredCondition condition) const {
+ using Tegra::Shader::PredCondition;
+ static const std::unordered_map<PredCondition, const char*> PredicateComparisonStrings = {
+ {PredCondition::LessThan, "<"}, {PredCondition::Equal, "=="},
+ {PredCondition::LessEqual, "<="}, {PredCondition::GreaterThan, ">"},
+ {PredCondition::NotEqual, "!="}, {PredCondition::GreaterEqual, ">="},
+ };
+
+ auto comparison = PredicateComparisonStrings.find(condition);
+ ASSERT_MSG(comparison != PredicateComparisonStrings.end(),
+ "Unknown predicate comparison operation");
+ return comparison->second;
+ }
+
+ /**
+ * Returns the operator string to use to combine two predicates in the 'setp' family of
+ * instructions.
+ * @params operation The operator used in the 'setp'-family instruction.
+ * @returns String corresponding to the GLSL operator that matches the desired operator.
+ */
+ std::string GetPredicateCombiner(Tegra::Shader::PredOperation operation) const {
+ using Tegra::Shader::PredOperation;
+ static const std::unordered_map<PredOperation, const char*> PredicateOperationStrings = {
+ {PredOperation::And, "&&"},
+ {PredOperation::Or, "||"},
+ {PredOperation::Xor, "^^"},
+ };
+
+ auto op = PredicateOperationStrings.find(operation);
+ ASSERT_MSG(op != PredicateOperationStrings.end(), "Unknown predicate operation");
+ return op->second;
+ }
+
+ /*
+ * Returns whether the instruction at the specified offset is a 'sched' instruction.
+ * Sched instructions always appear before a sequence of 3 instructions.
+ */
+ bool IsSchedInstruction(u32 offset) const {
+ // sched instructions appear once every 4 instructions.
+ static constexpr size_t SchedPeriod = 4;
+ u32 absolute_offset = offset - main_offset;
+
+ return (absolute_offset % SchedPeriod) == 0;
+ }
+
+ void WriteLogicOperation(Register dest, LogicOperation logic_op, const std::string& op_a,
+ const std::string& op_b) {
+ switch (logic_op) {
+ case LogicOperation::And: {
+ regs.SetRegisterToInteger(dest, true, 0, '(' + op_a + " & " + op_b + ')', 1, 1);
+ break;
+ }
+ case LogicOperation::Or: {
+ regs.SetRegisterToInteger(dest, true, 0, '(' + op_a + " | " + op_b + ')', 1, 1);
+ break;
+ }
+ case LogicOperation::Xor: {
+ regs.SetRegisterToInteger(dest, true, 0, '(' + op_a + " ^ " + op_b + ')', 1, 1);
+ break;
+ }
+ case LogicOperation::PassB: {
+ regs.SetRegisterToInteger(dest, true, 0, op_b, 1, 1);
+ break;
+ }
+ default:
+ NGLOG_CRITICAL(HW_GPU, "Unimplemented logic operation: {}", static_cast<u32>(logic_op));
+ UNREACHABLE();
+ }
+ }
+
+ /**
+ * Compiles a single instruction from Tegra to GLSL.
+ * @param offset the offset of the Tegra shader instruction.
+ * @return the offset of the next instruction to execute. Usually it is the current offset
+ * + 1. If the current instruction always terminates the program, returns PROGRAM_END.
+ */
+ u32 CompileInstr(u32 offset) {
+ // Ignore sched instructions when generating code.
+ if (IsSchedInstruction(offset)) {
+ return offset + 1;
+ }
+
+ const Instruction instr = {program_code[offset]};
+ const auto opcode = OpCode::Decode(instr);
+
+ // Decoding failure
+ if (!opcode) {
+ NGLOG_CRITICAL(HW_GPU, "Unhandled instruction: {0:x}", instr.value);
+ UNREACHABLE();
+ }
+
+ shader.AddLine("// " + std::to_string(offset) + ": " + opcode->GetName());
+
+ using Tegra::Shader::Pred;
+ ASSERT_MSG(instr.pred.full_pred != Pred::NeverExecute,
+ "NeverExecute predicate not implemented");
+
+ if (instr.pred.pred_index != static_cast<u64>(Pred::UnusedIndex)) {
+ shader.AddLine("if (" +
+ GetPredicateCondition(instr.pred.pred_index, instr.negate_pred != 0) +
+ ')');
+ shader.AddLine('{');
+ ++shader.scope;
+ }
+
+ switch (opcode->GetType()) {
+ case OpCode::Type::Arithmetic: {
+ std::string op_a = instr.alu.negate_a ? "-" : "";
+ op_a += regs.GetRegisterAsFloat(instr.gpr8);
+ if (instr.alu.abs_a) {
+ op_a = "abs(" + op_a + ')';
+ }
+
+ std::string op_b = instr.alu.negate_b ? "-" : "";
+
+ if (instr.is_b_imm) {
+ op_b += GetImmediate19(instr);
+ } else {
+ if (instr.is_b_gpr) {
+ op_b += regs.GetRegisterAsFloat(instr.gpr20);
+ } else {
+ op_b += regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset,
+ GLSLRegister::Type::Float);
+ }
+ }
+
+ if (instr.alu.abs_b) {
+ op_b = "abs(" + op_b + ')';
+ }
+
+ switch (opcode->GetId()) {
+ case OpCode::Id::MOV_C:
+ case OpCode::Id::MOV_R: {
+ regs.SetRegisterToFloat(instr.gpr0, 0, op_b, 1, 1);
+ break;
+ }
+
+ case OpCode::Id::MOV32_IMM: {
+ // mov32i doesn't have abs or neg bits.
+ regs.SetRegisterToFloat(instr.gpr0, 0, GetImmediate32(instr), 1, 1);
+ break;
+ }
+ case OpCode::Id::FMUL_C:
+ case OpCode::Id::FMUL_R:
+ case OpCode::Id::FMUL_IMM: {
+ regs.SetRegisterToFloat(instr.gpr0, 0, op_a + " * " + op_b, 1, 1,
+ instr.alu.saturate_d);
+ break;
+ }
+ case OpCode::Id::FMUL32_IMM: {
+ // fmul32i doesn't have abs or neg bits.
+ regs.SetRegisterToFloat(
+ instr.gpr0, 0,
+ regs.GetRegisterAsFloat(instr.gpr8) + " * " + GetImmediate32(instr), 1, 1);
+ break;
+ }
+ case OpCode::Id::FADD_C:
+ case OpCode::Id::FADD_R:
+ case OpCode::Id::FADD_IMM: {
+ regs.SetRegisterToFloat(instr.gpr0, 0, op_a + " + " + op_b, 1, 1,
+ instr.alu.saturate_d);
+ break;
+ }
+ case OpCode::Id::MUFU: {
+ switch (instr.sub_op) {
+ case SubOp::Cos:
+ regs.SetRegisterToFloat(instr.gpr0, 0, "cos(" + op_a + ')', 1, 1,
+ instr.alu.saturate_d);
+ break;
+ case SubOp::Sin:
+ regs.SetRegisterToFloat(instr.gpr0, 0, "sin(" + op_a + ')', 1, 1,
+ instr.alu.saturate_d);
+ break;
+ case SubOp::Ex2:
+ regs.SetRegisterToFloat(instr.gpr0, 0, "exp2(" + op_a + ')', 1, 1,
+ instr.alu.saturate_d);
+ break;
+ case SubOp::Lg2:
+ regs.SetRegisterToFloat(instr.gpr0, 0, "log2(" + op_a + ')', 1, 1,
+ instr.alu.saturate_d);
+ break;
+ case SubOp::Rcp:
+ regs.SetRegisterToFloat(instr.gpr0, 0, "1.0 / " + op_a, 1, 1,
+ instr.alu.saturate_d);
+ break;
+ case SubOp::Rsq:
+ regs.SetRegisterToFloat(instr.gpr0, 0, "inversesqrt(" + op_a + ')', 1, 1,
+ instr.alu.saturate_d);
+ break;
+ case SubOp::Min:
+ regs.SetRegisterToFloat(instr.gpr0, 0, "min(" + op_a + "," + op_b + ')', 1, 1,
+ instr.alu.saturate_d);
+ break;
+ default:
+ NGLOG_CRITICAL(HW_GPU, "Unhandled MUFU sub op: {0:x}",
+ static_cast<unsigned>(instr.sub_op.Value()));
+ UNREACHABLE();
+ }
+ break;
+ }
+ case OpCode::Id::FMNMX_C:
+ case OpCode::Id::FMNMX_R:
+ case OpCode::Id::FMNMX_IMM: {
+ std::string condition =
+ GetPredicateCondition(instr.alu.fmnmx.pred, instr.alu.fmnmx.negate_pred != 0);
+ std::string parameters = op_a + ',' + op_b;
+ regs.SetRegisterToFloat(instr.gpr0, 0,
+ '(' + condition + ") ? min(" + parameters + ") : max(" +
+ parameters + ')',
+ 1, 1);
+ break;
+ }
+ case OpCode::Id::RRO_C:
+ case OpCode::Id::RRO_R:
+ case OpCode::Id::RRO_IMM: {
+ // Currently RRO is only implemented as a register move.
+ // Usage of `abs_b` and `negate_b` here should also be correct.
+ regs.SetRegisterToFloat(instr.gpr0, 0, op_b, 1, 1);
+ NGLOG_WARNING(HW_GPU, "RRO instruction is incomplete");
+ break;
+ }
+ default: {
+ NGLOG_CRITICAL(HW_GPU, "Unhandled arithmetic instruction: {}", opcode->GetName());
+ UNREACHABLE();
+ }
+ }
+ break;
+ }
+ case OpCode::Type::Bfe: {
+ ASSERT_MSG(!instr.bfe.negate_b, "Unimplemented");
+
+ std::string op_a = instr.bfe.negate_a ? "-" : "";
+ op_a += regs.GetRegisterAsInteger(instr.gpr8);
+
+ switch (opcode->GetId()) {
+ case OpCode::Id::BFE_IMM: {
+ std::string inner_shift =
+ '(' + op_a + " << " + std::to_string(instr.bfe.GetLeftShiftValue()) + ')';
+ std::string outer_shift =
+ '(' + inner_shift + " >> " +
+ std::to_string(instr.bfe.GetLeftShiftValue() + instr.bfe.shift_position) + ')';
+
+ regs.SetRegisterToInteger(instr.gpr0, true, 0, outer_shift, 1, 1);
+ break;
+ }
+ default: {
+ NGLOG_CRITICAL(HW_GPU, "Unhandled BFE instruction: {}", opcode->GetName());
+ UNREACHABLE();
+ }
+ }
+
+ break;
+ }
+
+ case OpCode::Type::Shift: {
+ std::string op_a = regs.GetRegisterAsInteger(instr.gpr8, 0, true);
+ std::string op_b;
+
+ if (instr.is_b_imm) {
+ op_b += '(' + std::to_string(instr.alu.GetSignedImm20_20()) + ')';
+ } else {
+ if (instr.is_b_gpr) {
+ op_b += regs.GetRegisterAsInteger(instr.gpr20);
+ } else {
+ op_b += regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset,
+ GLSLRegister::Type::Integer);
+ }
+ }
+
+ switch (opcode->GetId()) {
+ case OpCode::Id::SHR_C:
+ case OpCode::Id::SHR_R:
+ case OpCode::Id::SHR_IMM: {
+ if (!instr.shift.is_signed) {
+ // Logical shift right
+ op_a = "uint(" + op_a + ')';
+ }
+
+ // Cast to int is superfluous for arithmetic shift, it's only for a logical shift
+ regs.SetRegisterToInteger(instr.gpr0, true, 0, "int(" + op_a + " >> " + op_b + ')',
+ 1, 1);
+ break;
+ }
+ case OpCode::Id::SHL_C:
+ case OpCode::Id::SHL_R:
+ case OpCode::Id::SHL_IMM:
+ regs.SetRegisterToInteger(instr.gpr0, true, 0, op_a + " << " + op_b, 1, 1);
+ break;
+ default: {
+ NGLOG_CRITICAL(HW_GPU, "Unhandled shift instruction: {}", opcode->GetName());
+ UNREACHABLE();
+ }
+ }
+ break;
+ }
+
+ case OpCode::Type::ArithmeticIntegerImmediate: {
+ std::string op_a = regs.GetRegisterAsInteger(instr.gpr8);
+ std::string op_b = std::to_string(instr.alu.imm20_32.Value());
+
+ switch (opcode->GetId()) {
+ case OpCode::Id::IADD32I:
+ if (instr.iadd32i.negate_a)
+ op_a = "-(" + op_a + ')';
+
+ regs.SetRegisterToInteger(instr.gpr0, true, 0, op_a + " + " + op_b, 1, 1,
+ instr.iadd32i.saturate != 0);
+ break;
+ case OpCode::Id::LOP32I: {
+ if (instr.alu.lop32i.invert_a)
+ op_a = "~(" + op_a + ')';
+
+ if (instr.alu.lop32i.invert_b)
+ op_b = "~(" + op_b + ')';
+
+ WriteLogicOperation(instr.gpr0, instr.alu.lop32i.operation, op_a, op_b);
+ break;
+ }
+ default: {
+ NGLOG_CRITICAL(HW_GPU, "Unhandled ArithmeticIntegerImmediate instruction: {}",
+ opcode->GetName());
+ UNREACHABLE();
+ }
+ }
+ break;
+ }
+ case OpCode::Type::ArithmeticInteger: {
+ std::string op_a = regs.GetRegisterAsInteger(instr.gpr8);
+ std::string op_b;
+ if (instr.is_b_imm) {
+ op_b += '(' + std::to_string(instr.alu.GetSignedImm20_20()) + ')';
+ } else {
+ if (instr.is_b_gpr) {
+ op_b += regs.GetRegisterAsInteger(instr.gpr20);
+ } else {
+ op_b += regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset,
+ GLSLRegister::Type::Integer);
+ }
+ }
+
+ switch (opcode->GetId()) {
+ case OpCode::Id::IADD_C:
+ case OpCode::Id::IADD_R:
+ case OpCode::Id::IADD_IMM: {
+ if (instr.alu_integer.negate_a)
+ op_a = "-(" + op_a + ')';
+
+ if (instr.alu_integer.negate_b)
+ op_b = "-(" + op_b + ')';
+
+ regs.SetRegisterToInteger(instr.gpr0, true, 0, op_a + " + " + op_b, 1, 1,
+ instr.alu.saturate_d);
+ break;
+ }
+ case OpCode::Id::ISCADD_C:
+ case OpCode::Id::ISCADD_R:
+ case OpCode::Id::ISCADD_IMM: {
+ if (instr.alu_integer.negate_a)
+ op_a = "-(" + op_a + ')';
+
+ if (instr.alu_integer.negate_b)
+ op_b = "-(" + op_b + ')';
+
+ std::string shift = std::to_string(instr.alu_integer.shift_amount.Value());
+
+ regs.SetRegisterToInteger(instr.gpr0, true, 0,
+ "((" + op_a + " << " + shift + ") + " + op_b + ')', 1, 1);
+ break;
+ }
+ case OpCode::Id::LOP_C:
+ case OpCode::Id::LOP_R:
+ case OpCode::Id::LOP_IMM: {
+ ASSERT_MSG(!instr.alu.lop.unk44, "Unimplemented");
+ ASSERT_MSG(instr.alu.lop.pred48 == Pred::UnusedIndex, "Unimplemented");
+
+ if (instr.alu.lop.invert_a)
+ op_a = "~(" + op_a + ')';
+
+ if (instr.alu.lop.invert_b)
+ op_b = "~(" + op_b + ')';
+
+ WriteLogicOperation(instr.gpr0, instr.alu.lop.operation, op_a, op_b);
+ break;
+ }
+ default: {
+ NGLOG_CRITICAL(HW_GPU, "Unhandled ArithmeticInteger instruction: {}",
+ opcode->GetName());
+ UNREACHABLE();
+ }
+ }
+
+ break;
+ }
+ case OpCode::Type::Ffma: {
+ std::string op_a = regs.GetRegisterAsFloat(instr.gpr8);
+ std::string op_b = instr.ffma.negate_b ? "-" : "";
+ std::string op_c = instr.ffma.negate_c ? "-" : "";
+
+ switch (opcode->GetId()) {
+ case OpCode::Id::FFMA_CR: {
+ op_b += regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset,
+ GLSLRegister::Type::Float);
+ op_c += regs.GetRegisterAsFloat(instr.gpr39);
+ break;
+ }
+ case OpCode::Id::FFMA_RR: {
+ op_b += regs.GetRegisterAsFloat(instr.gpr20);
+ op_c += regs.GetRegisterAsFloat(instr.gpr39);
+ break;
+ }
+ case OpCode::Id::FFMA_RC: {
+ op_b += regs.GetRegisterAsFloat(instr.gpr39);
+ op_c += regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset,
+ GLSLRegister::Type::Float);
+ break;
+ }
+ case OpCode::Id::FFMA_IMM: {
+ op_b += GetImmediate19(instr);
+ op_c += regs.GetRegisterAsFloat(instr.gpr39);
+ break;
+ }
+ default: {
+ NGLOG_CRITICAL(HW_GPU, "Unhandled FFMA instruction: {}", opcode->GetName());
+ UNREACHABLE();
+ }
+ }
+
+ regs.SetRegisterToFloat(instr.gpr0, 0, op_a + " * " + op_b + " + " + op_c, 1, 1,
+ instr.alu.saturate_d);
+ break;
+ }
+ case OpCode::Type::Conversion: {
+ ASSERT_MSG(!instr.conversion.negate_a, "Unimplemented");
+
+ switch (opcode->GetId()) {
+ case OpCode::Id::I2I_R: {
+ ASSERT_MSG(!instr.conversion.selector, "Unimplemented");
+
+ std::string op_a = regs.GetRegisterAsInteger(
+ instr.gpr20, 0, instr.conversion.is_input_signed, instr.conversion.src_size);
+
+ if (instr.conversion.abs_a) {
+ op_a = "abs(" + op_a + ')';
+ }
+
+ regs.SetRegisterToInteger(instr.gpr0, instr.conversion.is_output_signed, 0, op_a, 1,
+ 1, instr.alu.saturate_d, 0, instr.conversion.dest_size);
+ break;
+ }
+ case OpCode::Id::I2F_R: {
+ ASSERT_MSG(instr.conversion.dest_size == Register::Size::Word, "Unimplemented");
+ ASSERT_MSG(!instr.conversion.selector, "Unimplemented");
+ std::string op_a = regs.GetRegisterAsInteger(
+ instr.gpr20, 0, instr.conversion.is_input_signed, instr.conversion.src_size);
+
+ if (instr.conversion.abs_a) {
+ op_a = "abs(" + op_a + ')';
+ }
+
+ regs.SetRegisterToFloat(instr.gpr0, 0, op_a, 1, 1);
+ break;
+ }
+ case OpCode::Id::F2F_R: {
+ ASSERT_MSG(instr.conversion.dest_size == Register::Size::Word, "Unimplemented");
+ ASSERT_MSG(instr.conversion.src_size == Register::Size::Word, "Unimplemented");
+ std::string op_a = regs.GetRegisterAsFloat(instr.gpr20);
+
+ switch (instr.conversion.f2f.rounding) {
+ case Tegra::Shader::F2fRoundingOp::None:
+ break;
+ case Tegra::Shader::F2fRoundingOp::Floor:
+ op_a = "floor(" + op_a + ')';
+ break;
+ case Tegra::Shader::F2fRoundingOp::Ceil:
+ op_a = "ceil(" + op_a + ')';
+ break;
+ case Tegra::Shader::F2fRoundingOp::Trunc:
+ op_a = "trunc(" + op_a + ')';
+ break;
+ default:
+ NGLOG_CRITICAL(HW_GPU, "Unimplemented f2f rounding mode {}",
+ static_cast<u32>(instr.conversion.f2f.rounding.Value()));
+ UNREACHABLE();
+ break;
+ }
+
+ if (instr.conversion.abs_a) {
+ op_a = "abs(" + op_a + ')';
+ }
+
+ regs.SetRegisterToFloat(instr.gpr0, 0, op_a, 1, 1, instr.alu.saturate_d);
+ break;
+ }
+ case OpCode::Id::F2I_R: {
+ ASSERT_MSG(instr.conversion.src_size == Register::Size::Word, "Unimplemented");
+ std::string op_a = regs.GetRegisterAsFloat(instr.gpr20);
+
+ if (instr.conversion.abs_a) {
+ op_a = "abs(" + op_a + ')';
+ }
+
+ switch (instr.conversion.f2i.rounding) {
+ case Tegra::Shader::F2iRoundingOp::None:
+ break;
+ case Tegra::Shader::F2iRoundingOp::Floor:
+ op_a = "floor(" + op_a + ')';
+ break;
+ case Tegra::Shader::F2iRoundingOp::Ceil:
+ op_a = "ceil(" + op_a + ')';
+ break;
+ case Tegra::Shader::F2iRoundingOp::Trunc:
+ op_a = "trunc(" + op_a + ')';
+ break;
+ default:
+ NGLOG_CRITICAL(HW_GPU, "Unimplemented f2i rounding mode {}",
+ static_cast<u32>(instr.conversion.f2i.rounding.Value()));
+ UNREACHABLE();
+ break;
+ }
+
+ if (instr.conversion.is_output_signed) {
+ op_a = "int(" + op_a + ')';
+ } else {
+ op_a = "uint(" + op_a + ')';
+ }
+
+ regs.SetRegisterToInteger(instr.gpr0, instr.conversion.is_output_signed, 0, op_a, 1,
+ 1, false, 0, instr.conversion.dest_size);
+ break;
+ }
+ default: {
+ NGLOG_CRITICAL(HW_GPU, "Unhandled conversion instruction: {}", opcode->GetName());
+ UNREACHABLE();
+ }
+ }
+ break;
+ }
+ case OpCode::Type::Memory: {
+ switch (opcode->GetId()) {
+ case OpCode::Id::LD_A: {
+ ASSERT_MSG(instr.attribute.fmt20.size == 0, "untested");
+ regs.SetRegisterToInputAttibute(instr.gpr0, instr.attribute.fmt20.element,
+ instr.attribute.fmt20.index);
+ break;
+ }
+ case OpCode::Id::LD_C: {
+ ASSERT_MSG(instr.ld_c.unknown == 0, "Unimplemented");
+
+ std::string op_a =
+ regs.GetUniformIndirect(instr.cbuf36.index, instr.cbuf36.offset + 0, instr.gpr8,
+ GLSLRegister::Type::Float);
+ std::string op_b =
+ regs.GetUniformIndirect(instr.cbuf36.index, instr.cbuf36.offset + 4, instr.gpr8,
+ GLSLRegister::Type::Float);
+
+ switch (instr.ld_c.type.Value()) {
+ case Tegra::Shader::UniformType::Single:
+ regs.SetRegisterToFloat(instr.gpr0, 0, op_a, 1, 1);
+ break;
+
+ case Tegra::Shader::UniformType::Double:
+ regs.SetRegisterToFloat(instr.gpr0, 0, op_a, 1, 1);
+ regs.SetRegisterToFloat(instr.gpr0.Value() + 1, 0, op_b, 1, 1);
+ break;
+
+ default:
+ NGLOG_CRITICAL(HW_GPU, "Unhandled type: {}",
+ static_cast<unsigned>(instr.ld_c.type.Value()));
+ UNREACHABLE();
+ }
+ break;
+ }
+ case OpCode::Id::ST_A: {
+ ASSERT_MSG(instr.attribute.fmt20.size == 0, "untested");
+ regs.SetOutputAttributeToRegister(instr.attribute.fmt20.index,
+ instr.attribute.fmt20.element, instr.gpr0);
+ break;
+ }
+ case OpCode::Id::TEX: {
+ const std::string op_a = regs.GetRegisterAsFloat(instr.gpr8);
+ const std::string op_b = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
+ const std::string sampler = GetSampler(instr.sampler);
+ const std::string coord = "vec2 coords = vec2(" + op_a + ", " + op_b + ");";
+ // Add an extra scope and declare the texture coords inside to prevent
+ // overwriting them in case they are used as outputs of the texs instruction.
+ shader.AddLine("{");
+ ++shader.scope;
+ shader.AddLine(coord);
+ const std::string texture = "texture(" + sampler + ", coords)";
+
+ size_t dest_elem{};
+ for (size_t elem = 0; elem < 4; ++elem) {
+ if (!instr.tex.IsComponentEnabled(elem)) {
+ // Skip disabled components
+ continue;
+ }
+ regs.SetRegisterToFloat(instr.gpr0, elem, texture, 1, 4, false, dest_elem);
+ ++dest_elem;
+ }
+ --shader.scope;
+ shader.AddLine("}");
+ break;
+ }
+ case OpCode::Id::TEXS: {
+ const std::string op_a = regs.GetRegisterAsFloat(instr.gpr8);
+ const std::string op_b = regs.GetRegisterAsFloat(instr.gpr20);
+ const std::string sampler = GetSampler(instr.sampler);
+ const std::string coord = "vec2 coords = vec2(" + op_a + ", " + op_b + ");";
+ // Add an extra scope and declare the texture coords inside to prevent
+ // overwriting them in case they are used as outputs of the texs instruction.
+ shader.AddLine("{");
+ ++shader.scope;
+ shader.AddLine(coord);
+ const std::string texture = "texture(" + sampler + ", coords)";
+
+ // TEXS has two destination registers. RG goes into gpr0+0 and gpr0+1, and BA
+ // goes into gpr28+0 and gpr28+1
+ size_t offset{};
+
+ for (const auto& dest : {instr.gpr0.Value(), instr.gpr28.Value()}) {
+ for (unsigned elem = 0; elem < 2; ++elem) {
+ if (!instr.texs.IsComponentEnabled(elem)) {
+ // Skip disabled components
+ continue;
+ }
+ regs.SetRegisterToFloat(dest, elem + offset, texture, 1, 4, false, elem);
+ }
+
+ if (!instr.texs.HasTwoDestinations()) {
+ // Skip the second destination
+ break;
+ }
+
+ offset += 2;
+ }
+ --shader.scope;
+ shader.AddLine("}");
+ break;
+ }
+ default: {
+ NGLOG_CRITICAL(HW_GPU, "Unhandled memory instruction: {}", opcode->GetName());
+ UNREACHABLE();
+ }
+ }
+ break;
+ }
+ case OpCode::Type::FloatSetPredicate: {
+ std::string op_a = instr.fsetp.neg_a ? "-" : "";
+ op_a += regs.GetRegisterAsFloat(instr.gpr8);
+
+ if (instr.fsetp.abs_a) {
+ op_a = "abs(" + op_a + ')';
+ }
+
+ std::string op_b{};
+
+ if (instr.is_b_imm) {
+ if (instr.fsetp.neg_b) {
+ // Only the immediate version of fsetp has a neg_b bit.
+ op_b += '-';
+ }
+ op_b += '(' + GetImmediate19(instr) + ')';
+ } else {
+ if (instr.is_b_gpr) {
+ op_b += regs.GetRegisterAsFloat(instr.gpr20);
+ } else {
+ op_b += regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset,
+ GLSLRegister::Type::Float);
+ }
+ }
+
+ if (instr.fsetp.abs_b) {
+ op_b = "abs(" + op_b + ')';
+ }
+
+ using Tegra::Shader::Pred;
+ // We can't use the constant predicate as destination.
+ ASSERT(instr.fsetp.pred3 != static_cast<u64>(Pred::UnusedIndex));
+
+ std::string second_pred =
+ GetPredicateCondition(instr.fsetp.pred39, instr.fsetp.neg_pred != 0);
+
+ std::string comparator = GetPredicateComparison(instr.fsetp.cond);
+ std::string combiner = GetPredicateCombiner(instr.fsetp.op);
+
+ std::string predicate = '(' + op_a + ") " + comparator + " (" + op_b + ')';
+ // Set the primary predicate to the result of Predicate OP SecondPredicate
+ SetPredicate(instr.fsetp.pred3,
+ '(' + predicate + ") " + combiner + " (" + second_pred + ')');
+
+ if (instr.fsetp.pred0 != static_cast<u64>(Pred::UnusedIndex)) {
+ // Set the secondary predicate to the result of !Predicate OP SecondPredicate,
+ // if enabled
+ SetPredicate(instr.fsetp.pred0,
+ "!(" + predicate + ") " + combiner + " (" + second_pred + ')');
+ }
+ break;
+ }
+ case OpCode::Type::IntegerSetPredicate: {
+ std::string op_a = regs.GetRegisterAsInteger(instr.gpr8, 0, instr.isetp.is_signed);
+ std::string op_b;
+
+ if (instr.is_b_imm) {
+ op_b += '(' + std::to_string(instr.alu.GetSignedImm20_20()) + ')';
+ } else {
+ if (instr.is_b_gpr) {
+ op_b += regs.GetRegisterAsInteger(instr.gpr20, 0, instr.isetp.is_signed);
+ } else {
+ op_b += regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset,
+ GLSLRegister::Type::Integer);
+ }
+ }
+
+ using Tegra::Shader::Pred;
+ // We can't use the constant predicate as destination.
+ ASSERT(instr.isetp.pred3 != static_cast<u64>(Pred::UnusedIndex));
+
+ std::string second_pred =
+ GetPredicateCondition(instr.isetp.pred39, instr.isetp.neg_pred != 0);
+
+ std::string comparator = GetPredicateComparison(instr.isetp.cond);
+ std::string combiner = GetPredicateCombiner(instr.isetp.op);
+
+ std::string predicate = '(' + op_a + ") " + comparator + " (" + op_b + ')';
+ // Set the primary predicate to the result of Predicate OP SecondPredicate
+ SetPredicate(instr.isetp.pred3,
+ '(' + predicate + ") " + combiner + " (" + second_pred + ')');
+
+ if (instr.isetp.pred0 != static_cast<u64>(Pred::UnusedIndex)) {
+ // Set the secondary predicate to the result of !Predicate OP SecondPredicate,
+ // if enabled
+ SetPredicate(instr.isetp.pred0,
+ "!(" + predicate + ") " + combiner + " (" + second_pred + ')');
+ }
+ break;
+ }
+ case OpCode::Type::FloatSet: {
+ std::string op_a = instr.fset.neg_a ? "-" : "";
+ op_a += regs.GetRegisterAsFloat(instr.gpr8);
+
+ if (instr.fset.abs_a) {
+ op_a = "abs(" + op_a + ')';
+ }
+
+ std::string op_b = instr.fset.neg_b ? "-" : "";
+
+ if (instr.is_b_imm) {
+ std::string imm = GetImmediate19(instr);
+ if (instr.fset.neg_imm)
+ op_b += "(-" + imm + ')';
+ else
+ op_b += imm;
+ } else {
+ if (instr.is_b_gpr) {
+ op_b += regs.GetRegisterAsFloat(instr.gpr20);
+ } else {
+ op_b += regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset,
+ GLSLRegister::Type::Float);
+ }
+ }
+
+ if (instr.fset.abs_b) {
+ op_b = "abs(" + op_b + ')';
+ }
+
+ // The fset instruction sets a register to 1.0 or -1 (depending on the bf bit) if the
+ // condition is true, and to 0 otherwise.
+ std::string second_pred =
+ GetPredicateCondition(instr.fset.pred39, instr.fset.neg_pred != 0);
+
+ std::string comparator = GetPredicateComparison(instr.fset.cond);
+ std::string combiner = GetPredicateCombiner(instr.fset.op);
+
+ std::string predicate = "(((" + op_a + ") " + comparator + " (" + op_b + ")) " +
+ combiner + " (" + second_pred + "))";
+
+ if (instr.fset.bf) {
+ regs.SetRegisterToFloat(instr.gpr0, 0, predicate + " ? 1.0 : 0.0", 1, 1);
+ } else {
+ regs.SetRegisterToInteger(instr.gpr0, false, 0, predicate + " ? 0xFFFFFFFF : 0", 1,
+ 1);
+ }
+ break;
+ }
+ case OpCode::Type::IntegerSet: {
+ std::string op_a = regs.GetRegisterAsInteger(instr.gpr8, 0, instr.iset.is_signed);
+
+ std::string op_b;
+
+ if (instr.is_b_imm) {
+ op_b = std::to_string(instr.alu.GetSignedImm20_20());
+ } else {
+ if (instr.is_b_gpr) {
+ op_b = regs.GetRegisterAsInteger(instr.gpr20, 0, instr.iset.is_signed);
+ } else {
+ op_b = regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset,
+ GLSLRegister::Type::Integer);
+ }
+ }
+
+ // The iset instruction sets a register to 1.0 or -1 (depending on the bf bit) if the
+ // condition is true, and to 0 otherwise.
+ std::string second_pred =
+ GetPredicateCondition(instr.iset.pred39, instr.iset.neg_pred != 0);
+
+ std::string comparator = GetPredicateComparison(instr.iset.cond);
+ std::string combiner = GetPredicateCombiner(instr.iset.op);
+
+ std::string predicate = "(((" + op_a + ") " + comparator + " (" + op_b + ")) " +
+ combiner + " (" + second_pred + "))";
+
+ if (instr.iset.bf) {
+ regs.SetRegisterToFloat(instr.gpr0, 0, predicate + " ? 1.0 : 0.0", 1, 1);
+ } else {
+ regs.SetRegisterToInteger(instr.gpr0, false, 0, predicate + " ? 0xFFFFFFFF : 0", 1,
+ 1);
+ }
+ break;
+ }
+ default: {
+ switch (opcode->GetId()) {
+ case OpCode::Id::EXIT: {
+ // Final color output is currently hardcoded to GPR0-3 for fragment shaders
+ if (stage == Maxwell3D::Regs::ShaderStage::Fragment) {
+ shader.AddLine("color.r = " + regs.GetRegisterAsFloat(0) + ';');
+ shader.AddLine("color.g = " + regs.GetRegisterAsFloat(1) + ';');
+ shader.AddLine("color.b = " + regs.GetRegisterAsFloat(2) + ';');
+ shader.AddLine("color.a = " + regs.GetRegisterAsFloat(3) + ';');
+ }
+
+ shader.AddLine("return true;");
+ if (instr.pred.pred_index == static_cast<u64>(Pred::UnusedIndex)) {
+ // If this is an unconditional exit then just end processing here, otherwise
+ // we have to account for the possibility of the condition not being met, so
+ // continue processing the next instruction.
+ offset = PROGRAM_END - 1;
+ }
+ break;
+ }
+ case OpCode::Id::KIL: {
+ shader.AddLine("discard;");
+ break;
+ }
+ case OpCode::Id::BRA: {
+ ASSERT_MSG(instr.bra.constant_buffer == 0,
+ "BRA with constant buffers are not implemented");
+ u32 target = offset + instr.bra.GetBranchTarget();
+ shader.AddLine("{ jmp_to = " + std::to_string(target) + "u; break; }");
+ break;
+ }
+ case OpCode::Id::IPA: {
+ const auto& attribute = instr.attribute.fmt28;
+ regs.SetRegisterToInputAttibute(instr.gpr0, attribute.element, attribute.index);
+ break;
+ }
+ case OpCode::Id::SSY: {
+ // The SSY opcode tells the GPU where to re-converge divergent execution paths, we
+ // can ignore this when generating GLSL code.
+ break;
+ }
+ default: {
+ NGLOG_CRITICAL(HW_GPU, "Unhandled instruction: {}", opcode->GetName());
+ UNREACHABLE();
+ }
+ }
+
+ break;
+ }
+ }
+
+ // Close the predicate condition scope.
+ if (instr.pred.pred_index != static_cast<u64>(Pred::UnusedIndex)) {
+ --shader.scope;
+ shader.AddLine('}');
+ }
+
+ return offset + 1;
+ }
+
+ /**
+ * Compiles a range of instructions from Tegra to GLSL.
+ * @param begin the offset of the starting instruction.
+ * @param end the offset where the compilation should stop (exclusive).
+ * @return the offset of the next instruction to compile. PROGRAM_END if the program
+ * terminates.
+ */
+ u32 CompileRange(u32 begin, u32 end) {
+ u32 program_counter;
+ for (program_counter = begin; program_counter < (begin > end ? PROGRAM_END : end);) {
+ program_counter = CompileInstr(program_counter);
+ }
+ return program_counter;
+ }
+
+ void Generate() {
+ // Add declarations for all subroutines
+ for (const auto& subroutine : subroutines) {
+ shader.AddLine("bool " + subroutine.GetName() + "();");
+ }
+ shader.AddNewLine();
+
+ // Add the main entry point
+ shader.AddLine("bool exec_shader() {");
+ ++shader.scope;
+ CallSubroutine(GetSubroutine(main_offset, PROGRAM_END));
+ --shader.scope;
+ shader.AddLine("}\n");
+
+ // Add definitions for all subroutines
+ for (const auto& subroutine : subroutines) {
+ std::set<u32> labels = subroutine.labels;
+
+ shader.AddLine("bool " + subroutine.GetName() + "() {");
+ ++shader.scope;
+
+ if (labels.empty()) {
+ if (CompileRange(subroutine.begin, subroutine.end) != PROGRAM_END) {
+ shader.AddLine("return false;");
+ }
+ } else {
+ labels.insert(subroutine.begin);
+ shader.AddLine("uint jmp_to = " + std::to_string(subroutine.begin) + "u;");
+ shader.AddLine("while (true) {");
+ ++shader.scope;
+
+ shader.AddLine("switch (jmp_to) {");
+
+ for (auto label : labels) {
+ shader.AddLine("case " + std::to_string(label) + "u: {");
+ ++shader.scope;
+
+ auto next_it = labels.lower_bound(label + 1);
+ u32 next_label = next_it == labels.end() ? subroutine.end : *next_it;
+
+ u32 compile_end = CompileRange(label, next_label);
+ if (compile_end > next_label && compile_end != PROGRAM_END) {
+ // This happens only when there is a label inside a IF/LOOP block
+ shader.AddLine("{ jmp_to = " + std::to_string(compile_end) + "u; break; }");
+ labels.emplace(compile_end);
+ }
+
+ --shader.scope;
+ shader.AddLine('}');
+ }
+
+ shader.AddLine("default: return false;");
+ shader.AddLine('}');
+
+ --shader.scope;
+ shader.AddLine('}');
+
+ shader.AddLine("return false;");
+ }
+
+ --shader.scope;
+ shader.AddLine("}\n");
+
+ DEBUG_ASSERT(shader.scope == 0);
+ }
+
+ GenerateDeclarations();
+ }
+
+ /// Add declarations for registers
+ void GenerateDeclarations() {
+ regs.GenerateDeclarations();
+
+ for (const auto& pred : declr_predicates) {
+ declarations.AddLine("bool " + pred + " = false;");
+ }
+ declarations.AddNewLine();
+ }
+
+private:
+ const std::set<Subroutine>& subroutines;
+ const ProgramCode& program_code;
+ const u32 main_offset;
+ Maxwell3D::Regs::ShaderStage stage;
+
+ ShaderWriter shader;
+ ShaderWriter declarations;
+ GLSLRegisterManager regs{shader, declarations, stage};
+
+ // Declarations
+ std::set<std::string> declr_predicates;
+}; // namespace Decompiler
+
+std::string GetCommonDeclarations() {
+ return "bool exec_shader();";
+}
+
+boost::optional<ProgramResult> DecompileProgram(const ProgramCode& program_code, u32 main_offset,
+ Maxwell3D::Regs::ShaderStage stage) {
+ try {
+ auto subroutines = ControlFlowAnalyzer(program_code, main_offset).GetSubroutines();
+ GLSLGenerator generator(subroutines, program_code, main_offset, stage);
+ return ProgramResult{generator.GetShaderCode(), generator.GetEntries()};
+ } catch (const DecompileFail& exception) {
+ NGLOG_ERROR(HW_GPU, "Shader decompilation failed: {}", exception.what());
+ }
+ return boost::none;
+}
+
+} // namespace Decompiler
+} // namespace GLShader
diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.h b/src/video_core/renderer_opengl/gl_shader_decompiler.h
new file mode 100644
index 000000000..382c76b7a
--- /dev/null
+++ b/src/video_core/renderer_opengl/gl_shader_decompiler.h
@@ -0,0 +1,26 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <functional>
+#include <string>
+#include <boost/optional.hpp>
+#include "common/common_types.h"
+#include "video_core/engines/maxwell_3d.h"
+#include "video_core/renderer_opengl/gl_shader_gen.h"
+
+namespace GLShader {
+namespace Decompiler {
+
+using Tegra::Engines::Maxwell3D;
+
+std::string GetCommonDeclarations();
+
+boost::optional<ProgramResult> DecompileProgram(const ProgramCode& program_code, u32 main_offset,
+ Maxwell3D::Regs::ShaderStage stage);
+
+} // namespace Decompiler
+} // namespace GLShader
diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp
new file mode 100644
index 000000000..c1e6fac9f
--- /dev/null
+++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp
@@ -0,0 +1,78 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#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"
+
+namespace GLShader {
+
+using Tegra::Engines::Maxwell3D;
+
+static constexpr u32 PROGRAM_OFFSET{10};
+
+ProgramResult GenerateVertexShader(const ShaderSetup& setup, const MaxwellVSConfig& config) {
+ std::string out = "#version 430 core\n";
+ out += "#extension GL_ARB_separate_shader_objects : enable\n\n";
+ out += Decompiler::GetCommonDeclarations();
+
+ ProgramResult program = Decompiler::DecompileProgram(setup.program_code, PROGRAM_OFFSET,
+ Maxwell3D::Regs::ShaderStage::Vertex)
+ .get_value_or({});
+ out += R"(
+
+out gl_PerVertex {
+ vec4 gl_Position;
+};
+
+out vec4 position;
+
+layout (std140) uniform vs_config {
+ vec4 viewport_flip;
+};
+
+void main() {
+ exec_shader();
+
+ // 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
+ position.w = 1.0;
+}
+)";
+ out += program.first;
+ return {out, program.second};
+}
+
+ProgramResult GenerateFragmentShader(const ShaderSetup& setup, const MaxwellFSConfig& config) {
+ std::string out = "#version 430 core\n";
+ out += "#extension GL_ARB_separate_shader_objects : enable\n\n";
+ out += Decompiler::GetCommonDeclarations();
+
+ ProgramResult program = Decompiler::DecompileProgram(setup.program_code, PROGRAM_OFFSET,
+ Maxwell3D::Regs::ShaderStage::Fragment)
+ .get_value_or({});
+ out += R"(
+
+in vec4 position;
+out vec4 color;
+
+layout (std140) uniform fs_config {
+ vec4 viewport_flip;
+};
+
+void main() {
+ exec_shader();
+}
+
+)";
+ out += program.first;
+ return {out, program.second};
+}
+
+} // namespace GLShader
diff --git a/src/video_core/renderer_opengl/gl_shader_gen.h b/src/video_core/renderer_opengl/gl_shader_gen.h
new file mode 100644
index 000000000..ed890e0f9
--- /dev/null
+++ b/src/video_core/renderer_opengl/gl_shader_gen.h
@@ -0,0 +1,185 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <string>
+#include <type_traits>
+#include <utility>
+#include <vector>
+#include "common/common_types.h"
+#include "common/hash.h"
+
+namespace GLShader {
+
+constexpr size_t MAX_PROGRAM_CODE_LENGTH{0x1000};
+
+using ProgramCode = std::array<u64, MAX_PROGRAM_CODE_LENGTH>;
+
+class ConstBufferEntry {
+ using Maxwell = Tegra::Engines::Maxwell3D::Regs;
+
+public:
+ void MarkAsUsed(u64 index, u64 offset, Maxwell::ShaderStage stage) {
+ is_used = true;
+ this->index = static_cast<unsigned>(index);
+ this->stage = stage;
+ max_offset = std::max(max_offset, static_cast<unsigned>(offset));
+ }
+
+ void MarkAsUsedIndirect(u64 index, Maxwell::ShaderStage stage) {
+ is_used = true;
+ is_indirect = true;
+ this->index = static_cast<unsigned>(index);
+ this->stage = stage;
+ }
+
+ bool IsUsed() const {
+ return is_used;
+ }
+
+ bool IsIndirect() const {
+ return is_indirect;
+ }
+
+ unsigned GetIndex() const {
+ return index;
+ }
+
+ unsigned GetSize() const {
+ return max_offset + 1;
+ }
+
+ std::string GetName() const {
+ return BufferBaseNames[static_cast<size_t>(stage)] + std::to_string(index);
+ }
+
+private:
+ static constexpr std::array<const char*, Maxwell::MaxShaderStage> BufferBaseNames = {
+ "buffer_vs_c", "buffer_tessc_c", "buffer_tesse_c", "buffer_gs_c", "buffer_fs_c",
+ };
+
+ bool is_used{};
+ bool is_indirect{};
+ unsigned index{};
+ unsigned max_offset{};
+ Maxwell::ShaderStage stage;
+};
+
+class SamplerEntry {
+ using Maxwell = Tegra::Engines::Maxwell3D::Regs;
+
+public:
+ SamplerEntry(Maxwell::ShaderStage stage, size_t offset, size_t index)
+ : offset(offset), stage(stage), sampler_index(index) {}
+
+ size_t GetOffset() const {
+ return offset;
+ }
+
+ size_t GetIndex() const {
+ return sampler_index;
+ }
+
+ Maxwell::ShaderStage GetStage() const {
+ return stage;
+ }
+
+ std::string GetName() const {
+ return std::string(TextureSamplerNames[static_cast<size_t>(stage)]) + '[' +
+ std::to_string(sampler_index) + ']';
+ }
+
+ static std::string GetArrayName(Maxwell::ShaderStage stage) {
+ return TextureSamplerNames[static_cast<size_t>(stage)];
+ }
+
+private:
+ static constexpr std::array<const char*, Maxwell::MaxShaderStage> TextureSamplerNames = {
+ "tex_vs", "tex_tessc", "tex_tesse", "tex_gs", "tex_fs",
+ };
+ /// Offset in TSC memory from which to read the sampler object, as specified by the sampling
+ /// instruction.
+ size_t offset;
+ Maxwell::ShaderStage stage; ///< Shader stage where this sampler was used.
+ size_t sampler_index; ///< Value used to index into the generated GLSL sampler array.
+};
+
+struct ShaderEntries {
+ std::vector<ConstBufferEntry> const_buffer_entries;
+ std::vector<SamplerEntry> texture_samplers;
+};
+
+using ProgramResult = std::pair<std::string, ShaderEntries>;
+
+struct ShaderSetup {
+ ShaderSetup(ProgramCode&& program_code) : program_code(std::move(program_code)) {}
+
+ ProgramCode program_code;
+ bool program_code_hash_dirty = true;
+
+ u64 GetProgramCodeHash() {
+ if (program_code_hash_dirty) {
+ program_code_hash = Common::ComputeHash64(&program_code, sizeof(program_code));
+ program_code_hash_dirty = false;
+ }
+ return program_code_hash;
+ }
+
+private:
+ u64 program_code_hash{};
+};
+
+struct MaxwellShaderConfigCommon {
+ void Init(ShaderSetup& setup) {
+ program_hash = setup.GetProgramCodeHash();
+ }
+
+ u64 program_hash;
+};
+
+struct MaxwellVSConfig : Common::HashableStruct<MaxwellShaderConfigCommon> {
+ explicit MaxwellVSConfig(ShaderSetup& setup) {
+ state.Init(setup);
+ }
+};
+
+struct MaxwellFSConfig : Common::HashableStruct<MaxwellShaderConfigCommon> {
+ explicit MaxwellFSConfig(ShaderSetup& setup) {
+ state.Init(setup);
+ }
+};
+
+/**
+ * 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, const MaxwellVSConfig& config);
+
+/**
+ * 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, const MaxwellFSConfig& config);
+
+} // namespace GLShader
+
+namespace std {
+
+template <>
+struct hash<GLShader::MaxwellVSConfig> {
+ size_t operator()(const GLShader::MaxwellVSConfig& k) const {
+ return k.Hash();
+ }
+};
+
+template <>
+struct hash<GLShader::MaxwellFSConfig> {
+ size_t operator()(const GLShader::MaxwellFSConfig& k) const {
+ return k.Hash();
+ }
+};
+
+} // namespace std
diff --git a/src/video_core/renderer_opengl/gl_shader_manager.cpp b/src/video_core/renderer_opengl/gl_shader_manager.cpp
new file mode 100644
index 000000000..7c00beb33
--- /dev/null
+++ b/src/video_core/renderer_opengl/gl_shader_manager.cpp
@@ -0,0 +1,45 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/core.h"
+#include "core/hle/kernel/process.h"
+#include "video_core/engines/maxwell_3d.h"
+#include "video_core/renderer_opengl/gl_shader_manager.h"
+
+namespace GLShader {
+
+namespace Impl {
+void SetShaderUniformBlockBinding(GLuint shader, const char* name,
+ Maxwell3D::Regs::ShaderStage binding, size_t expected_size) {
+ GLuint ub_index = glGetUniformBlockIndex(shader, name);
+ if (ub_index != GL_INVALID_INDEX) {
+ GLint ub_size = 0;
+ glGetActiveUniformBlockiv(shader, ub_index, GL_UNIFORM_BLOCK_DATA_SIZE, &ub_size);
+ ASSERT_MSG(ub_size == expected_size,
+ "Uniform block size did not match! Got {}, expected {}",
+ static_cast<int>(ub_size), expected_size);
+ glUniformBlockBinding(shader, ub_index, static_cast<GLuint>(binding));
+ }
+}
+
+void SetShaderUniformBlockBindings(GLuint shader) {
+ SetShaderUniformBlockBinding(shader, "vs_config", Maxwell3D::Regs::ShaderStage::Vertex,
+ sizeof(MaxwellUniformData));
+ SetShaderUniformBlockBinding(shader, "gs_config", Maxwell3D::Regs::ShaderStage::Geometry,
+ sizeof(MaxwellUniformData));
+ SetShaderUniformBlockBinding(shader, "fs_config", Maxwell3D::Regs::ShaderStage::Fragment,
+ sizeof(MaxwellUniformData));
+}
+
+} // namespace Impl
+
+void MaxwellUniformData::SetFromRegs(const Maxwell3D::State::ShaderStageInfo& shader_stage) {
+ const auto& regs = Core::System().GetInstance().GPU().Maxwell3D().regs;
+
+ // TODO(bunnei): Support more than one viewport
+ viewport_flip[0] = regs.viewport_transform[0].scale_x < 0.0 ? -1.0 : 1.0;
+ viewport_flip[1] = regs.viewport_transform[0].scale_y < 0.0 ? -1.0 : 1.0;
+}
+
+} // namespace GLShader
diff --git a/src/video_core/renderer_opengl/gl_shader_manager.h b/src/video_core/renderer_opengl/gl_shader_manager.h
new file mode 100644
index 000000000..4295c20a6
--- /dev/null
+++ b/src/video_core/renderer_opengl/gl_shader_manager.h
@@ -0,0 +1,174 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <tuple>
+#include <unordered_map>
+#include <boost/functional/hash.hpp>
+#include <glad/glad.h>
+#include "video_core/renderer_opengl/gl_resource_manager.h"
+#include "video_core/renderer_opengl/gl_shader_gen.h"
+#include "video_core/renderer_opengl/maxwell_to_gl.h"
+
+namespace GLShader {
+
+/// Number of OpenGL texture samplers that can be used in the fragment shader
+static constexpr size_t NumTextureSamplers = 32;
+
+using Tegra::Engines::Maxwell3D;
+
+namespace Impl {
+void SetShaderUniformBlockBindings(GLuint shader);
+void SetShaderSamplerBindings(GLuint shader);
+} // namespace Impl
+
+/// 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 wether 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);
+ alignas(16) GLvec4 viewport_flip;
+};
+static_assert(sizeof(MaxwellUniformData) == 16, "MaxwellUniformData structure size is incorrect");
+static_assert(sizeof(MaxwellUniformData) < 16384,
+ "MaxwellUniformData structure must be less than 16kb as per the OpenGL spec");
+
+class OGLShaderStage {
+public:
+ OGLShaderStage() = default;
+
+ void Create(const ProgramResult& program_result, GLenum type) {
+ OGLShader shader;
+ shader.Create(program_result.first.c_str(), type);
+ program.Create(true, shader.handle);
+ Impl::SetShaderUniformBlockBindings(program.handle);
+ entries = program_result.second;
+ }
+ GLuint GetHandle() const {
+ return program.handle;
+ }
+
+ ShaderEntries GetEntries() const {
+ return entries;
+ }
+
+private:
+ OGLProgram program;
+ ShaderEntries entries;
+};
+
+// TODO(wwylele): beautify this doc
+// This is a shader cache designed for translating PICA shader to GLSL shader.
+// The double cache is needed because diffent KeyConfigType, which includes a hash of the code
+// region (including its leftover unused code) can generate the same GLSL code.
+template <typename KeyConfigType,
+ ProgramResult (*CodeGenerator)(const ShaderSetup&, const KeyConfigType&),
+ GLenum ShaderType>
+class ShaderCache {
+public:
+ ShaderCache() = default;
+
+ using Result = std::pair<GLuint, ShaderEntries>;
+
+ Result Get(const KeyConfigType& key, const ShaderSetup& setup) {
+ auto map_it = shader_map.find(key);
+ if (map_it == shader_map.end()) {
+ ProgramResult program = CodeGenerator(setup, key);
+
+ auto [iter, new_shader] = shader_cache.emplace(program.first, OGLShaderStage{});
+ OGLShaderStage& cached_shader = iter->second;
+ if (new_shader) {
+ cached_shader.Create(program, ShaderType);
+ }
+ shader_map[key] = &cached_shader;
+ return {cached_shader.GetHandle(), program.second};
+ } else {
+ return {map_it->second->GetHandle(), map_it->second->GetEntries()};
+ }
+ }
+
+private:
+ std::unordered_map<KeyConfigType, OGLShaderStage*> shader_map;
+ std::unordered_map<std::string, OGLShaderStage> shader_cache;
+};
+
+using VertexShaders = ShaderCache<MaxwellVSConfig, &GenerateVertexShader, GL_VERTEX_SHADER>;
+
+using FragmentShaders = ShaderCache<MaxwellFSConfig, &GenerateFragmentShader, GL_FRAGMENT_SHADER>;
+
+class ProgramManager {
+public:
+ ProgramManager() {
+ pipeline.Create();
+ }
+
+ ShaderEntries UseProgrammableVertexShader(const MaxwellVSConfig& config,
+ const ShaderSetup setup) {
+ ShaderEntries result;
+ std::tie(current.vs, result) = vertex_shaders.Get(config, setup);
+ return result;
+ }
+
+ ShaderEntries UseProgrammableFragmentShader(const MaxwellFSConfig& config,
+ const ShaderSetup setup) {
+ ShaderEntries result;
+ std::tie(current.fs, result) = fragment_shaders.Get(config, setup);
+ return result;
+ }
+
+ GLuint GetCurrentProgramStage(Maxwell3D::Regs::ShaderStage stage) {
+ switch (stage) {
+ case Maxwell3D::Regs::ShaderStage::Vertex:
+ return current.vs;
+ case Maxwell3D::Regs::ShaderStage::Fragment:
+ return current.fs;
+ }
+
+ UNREACHABLE();
+ }
+
+ void UseTrivialGeometryShader() {
+ current.gs = 0;
+ }
+
+ void ApplyTo(OpenGLState& state) {
+ // 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, current.vs);
+ glUseProgramStages(pipeline.handle, GL_GEOMETRY_SHADER_BIT, current.gs);
+ glUseProgramStages(pipeline.handle, GL_FRAGMENT_SHADER_BIT, current.fs);
+ state.draw.shader_program = 0;
+ state.draw.program_pipeline = pipeline.handle;
+ }
+
+private:
+ struct ShaderTuple {
+ GLuint vs = 0, gs = 0, fs = 0;
+ bool operator==(const ShaderTuple& rhs) const {
+ return std::tie(vs, gs, fs) == std::tie(rhs.vs, rhs.gs, rhs.fs);
+ }
+ struct Hash {
+ std::size_t operator()(const ShaderTuple& tuple) const {
+ std::size_t hash = 0;
+ boost::hash_combine(hash, tuple.vs);
+ boost::hash_combine(hash, tuple.gs);
+ boost::hash_combine(hash, tuple.fs);
+ return hash;
+ }
+ };
+ };
+ ShaderTuple current;
+ VertexShaders vertex_shaders;
+ FragmentShaders fragment_shaders;
+
+ std::unordered_map<ShaderTuple, OGLProgram, ShaderTuple::Hash> program_cache;
+ OGLPipeline pipeline;
+};
+
+} // namespace GLShader
diff --git a/src/video_core/renderer_opengl/gl_shader_util.cpp b/src/video_core/renderer_opengl/gl_shader_util.cpp
index 4da241d83..8568fface 100644
--- a/src/video_core/renderer_opengl/gl_shader_util.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_util.cpp
@@ -10,90 +10,41 @@
namespace GLShader {
-GLuint LoadProgram(const char* vertex_shader, const char* fragment_shader) {
-
- // Create the shaders
- GLuint vertex_shader_id = glCreateShader(GL_VERTEX_SHADER);
- GLuint fragment_shader_id = glCreateShader(GL_FRAGMENT_SHADER);
-
- GLint result = GL_FALSE;
- int info_log_length;
-
- // Compile Vertex Shader
- LOG_DEBUG(Render_OpenGL, "Compiling vertex shader...");
-
- glShaderSource(vertex_shader_id, 1, &vertex_shader, nullptr);
- glCompileShader(vertex_shader_id);
-
- // Check Vertex Shader
- glGetShaderiv(vertex_shader_id, GL_COMPILE_STATUS, &result);
- glGetShaderiv(vertex_shader_id, GL_INFO_LOG_LENGTH, &info_log_length);
-
- if (info_log_length > 1) {
- std::vector<char> vertex_shader_error(info_log_length);
- glGetShaderInfoLog(vertex_shader_id, info_log_length, nullptr, &vertex_shader_error[0]);
- if (result == GL_TRUE) {
- LOG_DEBUG(Render_OpenGL, "%s", &vertex_shader_error[0]);
- } else {
- LOG_ERROR(Render_OpenGL, "Error compiling vertex shader:\n%s", &vertex_shader_error[0]);
- }
- }
-
- // Compile Fragment Shader
- LOG_DEBUG(Render_OpenGL, "Compiling fragment shader...");
-
- glShaderSource(fragment_shader_id, 1, &fragment_shader, nullptr);
- glCompileShader(fragment_shader_id);
-
- // Check Fragment Shader
- glGetShaderiv(fragment_shader_id, GL_COMPILE_STATUS, &result);
- glGetShaderiv(fragment_shader_id, GL_INFO_LOG_LENGTH, &info_log_length);
-
- if (info_log_length > 1) {
- std::vector<char> fragment_shader_error(info_log_length);
- glGetShaderInfoLog(fragment_shader_id, info_log_length, nullptr, &fragment_shader_error[0]);
- if (result == GL_TRUE) {
- LOG_DEBUG(Render_OpenGL, "%s", &fragment_shader_error[0]);
- } else {
- LOG_ERROR(Render_OpenGL, "Error compiling fragment shader:\n%s",
- &fragment_shader_error[0]);
- }
+GLuint LoadShader(const char* source, GLenum type) {
+ const char* debug_type;
+ switch (type) {
+ case GL_VERTEX_SHADER:
+ debug_type = "vertex";
+ break;
+ case GL_GEOMETRY_SHADER:
+ debug_type = "geometry";
+ break;
+ case GL_FRAGMENT_SHADER:
+ debug_type = "fragment";
+ break;
+ default:
+ UNREACHABLE();
}
+ GLuint shader_id = glCreateShader(type);
+ glShaderSource(shader_id, 1, &source, nullptr);
+ NGLOG_DEBUG(Render_OpenGL, "Compiling {} shader...", debug_type);
+ glCompileShader(shader_id);
- // Link the program
- LOG_DEBUG(Render_OpenGL, "Linking program...");
-
- GLuint program_id = glCreateProgram();
- glAttachShader(program_id, vertex_shader_id);
- glAttachShader(program_id, fragment_shader_id);
-
- glLinkProgram(program_id);
-
- // Check the program
- glGetProgramiv(program_id, GL_LINK_STATUS, &result);
- glGetProgramiv(program_id, GL_INFO_LOG_LENGTH, &info_log_length);
+ GLint result = GL_FALSE;
+ GLint info_log_length;
+ glGetShaderiv(shader_id, GL_COMPILE_STATUS, &result);
+ glGetShaderiv(shader_id, GL_INFO_LOG_LENGTH, &info_log_length);
if (info_log_length > 1) {
- std::vector<char> program_error(info_log_length);
- glGetProgramInfoLog(program_id, info_log_length, nullptr, &program_error[0]);
+ std::string shader_error(info_log_length, ' ');
+ glGetShaderInfoLog(shader_id, info_log_length, nullptr, &shader_error[0]);
if (result == GL_TRUE) {
- LOG_DEBUG(Render_OpenGL, "%s", &program_error[0]);
+ NGLOG_DEBUG(Render_OpenGL, "{}", shader_error);
} else {
- LOG_ERROR(Render_OpenGL, "Error linking shader:\n%s", &program_error[0]);
+ NGLOG_ERROR(Render_OpenGL, "Error compiling {} shader:\n{}", debug_type, shader_error);
}
}
-
- // If the program linking failed at least one of the shaders was probably bad
- if (result == GL_FALSE) {
- LOG_ERROR(Render_OpenGL, "Vertex shader:\n%s", vertex_shader);
- LOG_ERROR(Render_OpenGL, "Fragment shader:\n%s", fragment_shader);
- }
- ASSERT_MSG(result == GL_TRUE, "Shader not linked");
-
- glDeleteShader(vertex_shader_id);
- glDeleteShader(fragment_shader_id);
-
- return program_id;
+ return shader_id;
}
} // namespace GLShader
diff --git a/src/video_core/renderer_opengl/gl_shader_util.h b/src/video_core/renderer_opengl/gl_shader_util.h
index a4bcffdfa..2036a06a9 100644
--- a/src/video_core/renderer_opengl/gl_shader_util.h
+++ b/src/video_core/renderer_opengl/gl_shader_util.h
@@ -4,16 +4,89 @@
#pragma once
+#include <string>
+#include <vector>
#include <glad/glad.h>
+#include "common/assert.h"
+#include "common/logging/log.h"
namespace GLShader {
/**
+ * Utility function to log the source code of a list of shaders.
+ * @param shaders The OpenGL shaders whose source we will print.
+ */
+template <typename... T>
+void LogShaderSource(T... shaders) {
+ auto shader_list = {shaders...};
+
+ for (const auto& shader : shader_list) {
+ if (shader == 0)
+ continue;
+
+ GLint source_length;
+ glGetShaderiv(shader, GL_SHADER_SOURCE_LENGTH, &source_length);
+
+ std::string source(source_length, ' ');
+ glGetShaderSource(shader, source_length, nullptr, &source[0]);
+ NGLOG_INFO(Render_OpenGL, "Shader source {}", source);
+ }
+}
+
+/**
+ * Utility function to create and compile an OpenGL GLSL shader
+ * @param source String of the GLSL shader program
+ * @param type Type of the shader (GL_VERTEX_SHADER, GL_GEOMETRY_SHADER or GL_FRAGMENT_SHADER)
+ */
+GLuint LoadShader(const char* source, GLenum type);
+
+/**
* Utility function to create and compile an OpenGL GLSL shader program (vertex + fragment shader)
- * @param vertex_shader String of the GLSL vertex shader program
- * @param fragment_shader String of the GLSL fragment shader program
- * @returns Handle of the newly created OpenGL shader object
+ * @param separable_program whether to create a separable program
+ * @param shaders ID of shaders to attach to the program
+ * @returns Handle of the newly created OpenGL program object
*/
-GLuint LoadProgram(const char* vertex_shader, const char* fragment_shader);
+template <typename... T>
+GLuint LoadProgram(bool separable_program, T... shaders) {
+ // Link the program
+ NGLOG_DEBUG(Render_OpenGL, "Linking program...");
+
+ GLuint program_id = glCreateProgram();
+
+ ((shaders == 0 ? (void)0 : glAttachShader(program_id, shaders)), ...);
+
+ if (separable_program) {
+ glProgramParameteri(program_id, GL_PROGRAM_SEPARABLE, GL_TRUE);
+ }
+
+ glLinkProgram(program_id);
+
+ // Check the program
+ GLint result = GL_FALSE;
+ GLint info_log_length;
+ glGetProgramiv(program_id, GL_LINK_STATUS, &result);
+ glGetProgramiv(program_id, GL_INFO_LOG_LENGTH, &info_log_length);
+
+ if (info_log_length > 1) {
+ std::string program_error(info_log_length, ' ');
+ glGetProgramInfoLog(program_id, info_log_length, nullptr, &program_error[0]);
+ if (result == GL_TRUE) {
+ NGLOG_DEBUG(Render_OpenGL, "{}", program_error);
+ } else {
+ NGLOG_ERROR(Render_OpenGL, "Error linking shader:\n{}", program_error);
+ }
+ }
+
+ if (result == GL_FALSE) {
+ // There was a problem linking the shader, print the source for debugging purposes.
+ LogShaderSource(shaders...);
+ }
+
+ ASSERT_MSG(result == GL_TRUE, "Shader not linked");
+
+ ((shaders == 0 ? (void)0 : glDetachShader(program_id, shaders)), ...);
+
+ return program_id;
+}
} // namespace GLShader
diff --git a/src/video_core/renderer_opengl/gl_state.cpp b/src/video_core/renderer_opengl/gl_state.cpp
index 5770ae08f..44f0c8a01 100644
--- a/src/video_core/renderer_opengl/gl_state.cpp
+++ b/src/video_core/renderer_opengl/gl_state.cpp
@@ -2,8 +2,8 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <iterator>
#include <glad/glad.h>
-#include "common/common_funcs.h"
#include "common/logging/log.h"
#include "video_core/renderer_opengl/gl_state.h"
@@ -33,7 +33,7 @@ OpenGLState::OpenGLState() {
stencil.action_depth_pass = GL_KEEP;
stencil.action_stencil_fail = GL_KEEP;
- blend.enabled = false;
+ blend.enabled = true;
blend.rgb_equation = GL_FUNC_ADD;
blend.a_equation = GL_FUNC_ADD;
blend.src_rgb_func = GL_ONE;
@@ -50,6 +50,10 @@ OpenGLState::OpenGLState() {
for (auto& texture_unit : texture_units) {
texture_unit.texture_2d = 0;
texture_unit.sampler = 0;
+ texture_unit.swizzle.r = GL_RED;
+ texture_unit.swizzle.g = GL_GREEN;
+ texture_unit.swizzle.b = GL_BLUE;
+ texture_unit.swizzle.a = GL_ALPHA;
}
lighting_lut.texture_buffer = 0;
@@ -68,6 +72,18 @@ OpenGLState::OpenGLState() {
draw.vertex_buffer = 0;
draw.uniform_buffer = 0;
draw.shader_program = 0;
+ draw.program_pipeline = 0;
+
+ scissor.enabled = false;
+ scissor.x = 0;
+ scissor.y = 0;
+ scissor.width = 0;
+ scissor.height = 0;
+
+ viewport.x = 0;
+ viewport.y = 0;
+ viewport.width = 0;
+ viewport.height = 0;
clip_distance = {};
}
@@ -148,9 +164,6 @@ void OpenGLState::Apply() const {
if (blend.enabled != cur_state.blend.enabled) {
if (blend.enabled) {
glEnable(GL_BLEND);
-
- cur_state.logic_op = GL_COPY;
- glLogicOp(cur_state.logic_op);
glDisable(GL_COLOR_LOGIC_OP);
} else {
glDisable(GL_BLEND);
@@ -183,20 +196,43 @@ void OpenGLState::Apply() const {
}
// Textures
- for (unsigned i = 0; i < ARRAY_SIZE(texture_units); ++i) {
+ for (size_t i = 0; i < std::size(texture_units); ++i) {
if (texture_units[i].texture_2d != cur_state.texture_units[i].texture_2d) {
- glActiveTexture(TextureUnits::PicaTexture(i).Enum());
+ glActiveTexture(TextureUnits::MaxwellTexture(i).Enum());
glBindTexture(GL_TEXTURE_2D, texture_units[i].texture_2d);
}
if (texture_units[i].sampler != cur_state.texture_units[i].sampler) {
glBindSampler(i, texture_units[i].sampler);
}
+ // Update the texture swizzle
+ if (texture_units[i].swizzle.r != cur_state.texture_units[i].swizzle.r ||
+ texture_units[i].swizzle.g != cur_state.texture_units[i].swizzle.g ||
+ texture_units[i].swizzle.b != cur_state.texture_units[i].swizzle.b ||
+ texture_units[i].swizzle.a != cur_state.texture_units[i].swizzle.a) {
+ std::array<GLint, 4> mask = {texture_units[i].swizzle.r, texture_units[i].swizzle.g,
+ texture_units[i].swizzle.b, texture_units[i].swizzle.a};
+ glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, mask.data());
+ }
+ }
+
+ // Constbuffers
+ for (u32 stage = 0; stage < draw.const_buffers.size(); ++stage) {
+ for (u32 buffer_id = 0; buffer_id < draw.const_buffers[stage].size(); ++buffer_id) {
+ auto& current = cur_state.draw.const_buffers[stage][buffer_id];
+ auto& new_state = draw.const_buffers[stage][buffer_id];
+ if (current.enabled != new_state.enabled || current.bindpoint != new_state.bindpoint ||
+ current.ssbo != new_state.ssbo) {
+ if (new_state.enabled) {
+ glBindBufferBase(GL_SHADER_STORAGE_BUFFER, new_state.bindpoint, new_state.ssbo);
+ }
+ }
+ }
}
// Lighting LUTs
if (lighting_lut.texture_buffer != cur_state.lighting_lut.texture_buffer) {
glActiveTexture(TextureUnits::LightingLUT.Enum());
- glBindTexture(GL_TEXTURE_BUFFER, cur_state.lighting_lut.texture_buffer);
+ glBindTexture(GL_TEXTURE_BUFFER, lighting_lut.texture_buffer);
}
// Fog LUT
@@ -263,6 +299,31 @@ void OpenGLState::Apply() const {
glUseProgram(draw.shader_program);
}
+ // Program pipeline
+ if (draw.program_pipeline != cur_state.draw.program_pipeline) {
+ glBindProgramPipeline(draw.program_pipeline);
+ }
+
+ // Scissor test
+ if (scissor.enabled != cur_state.scissor.enabled) {
+ if (scissor.enabled) {
+ glEnable(GL_SCISSOR_TEST);
+ } else {
+ glDisable(GL_SCISSOR_TEST);
+ }
+ }
+
+ if (scissor.x != cur_state.scissor.x || scissor.y != cur_state.scissor.y ||
+ scissor.width != cur_state.scissor.width || scissor.height != cur_state.scissor.height) {
+ glScissor(scissor.x, scissor.y, scissor.width, scissor.height);
+ }
+
+ if (viewport.x != cur_state.viewport.x || viewport.y != cur_state.viewport.y ||
+ viewport.width != cur_state.viewport.width ||
+ viewport.height != cur_state.viewport.height) {
+ glViewport(viewport.x, viewport.y, viewport.width, viewport.height);
+ }
+
// Clip distance
for (size_t i = 0; i < clip_distance.size(); ++i) {
if (clip_distance[i] != cur_state.clip_distance[i]) {
@@ -277,62 +338,75 @@ void OpenGLState::Apply() const {
cur_state = *this;
}
-void OpenGLState::ResetTexture(GLuint handle) {
- for (auto& unit : cur_state.texture_units) {
+OpenGLState& OpenGLState::ResetTexture(GLuint handle) {
+ for (auto& unit : texture_units) {
if (unit.texture_2d == handle) {
unit.texture_2d = 0;
}
}
- if (cur_state.lighting_lut.texture_buffer == handle)
- cur_state.lighting_lut.texture_buffer = 0;
- if (cur_state.fog_lut.texture_buffer == handle)
- cur_state.fog_lut.texture_buffer = 0;
- if (cur_state.proctex_noise_lut.texture_buffer == handle)
- cur_state.proctex_noise_lut.texture_buffer = 0;
- if (cur_state.proctex_color_map.texture_buffer == handle)
- cur_state.proctex_color_map.texture_buffer = 0;
- if (cur_state.proctex_alpha_map.texture_buffer == handle)
- cur_state.proctex_alpha_map.texture_buffer = 0;
- if (cur_state.proctex_lut.texture_buffer == handle)
- cur_state.proctex_lut.texture_buffer = 0;
- if (cur_state.proctex_diff_lut.texture_buffer == handle)
- cur_state.proctex_diff_lut.texture_buffer = 0;
+ if (lighting_lut.texture_buffer == handle)
+ lighting_lut.texture_buffer = 0;
+ if (fog_lut.texture_buffer == handle)
+ fog_lut.texture_buffer = 0;
+ if (proctex_noise_lut.texture_buffer == handle)
+ proctex_noise_lut.texture_buffer = 0;
+ if (proctex_color_map.texture_buffer == handle)
+ proctex_color_map.texture_buffer = 0;
+ if (proctex_alpha_map.texture_buffer == handle)
+ proctex_alpha_map.texture_buffer = 0;
+ if (proctex_lut.texture_buffer == handle)
+ proctex_lut.texture_buffer = 0;
+ if (proctex_diff_lut.texture_buffer == handle)
+ proctex_diff_lut.texture_buffer = 0;
+ return *this;
}
-void OpenGLState::ResetSampler(GLuint handle) {
- for (auto& unit : cur_state.texture_units) {
+OpenGLState& OpenGLState::ResetSampler(GLuint handle) {
+ for (auto& unit : texture_units) {
if (unit.sampler == handle) {
unit.sampler = 0;
}
}
+ return *this;
+}
+
+OpenGLState& OpenGLState::ResetProgram(GLuint handle) {
+ if (draw.shader_program == handle) {
+ draw.shader_program = 0;
+ }
+ return *this;
}
-void OpenGLState::ResetProgram(GLuint handle) {
- if (cur_state.draw.shader_program == handle) {
- cur_state.draw.shader_program = 0;
+OpenGLState& OpenGLState::ResetPipeline(GLuint handle) {
+ if (draw.program_pipeline == handle) {
+ draw.program_pipeline = 0;
}
+ return *this;
}
-void OpenGLState::ResetBuffer(GLuint handle) {
- if (cur_state.draw.vertex_buffer == handle) {
- cur_state.draw.vertex_buffer = 0;
+OpenGLState& OpenGLState::ResetBuffer(GLuint handle) {
+ if (draw.vertex_buffer == handle) {
+ draw.vertex_buffer = 0;
}
- if (cur_state.draw.uniform_buffer == handle) {
- cur_state.draw.uniform_buffer = 0;
+ if (draw.uniform_buffer == handle) {
+ draw.uniform_buffer = 0;
}
+ return *this;
}
-void OpenGLState::ResetVertexArray(GLuint handle) {
- if (cur_state.draw.vertex_array == handle) {
- cur_state.draw.vertex_array = 0;
+OpenGLState& OpenGLState::ResetVertexArray(GLuint handle) {
+ if (draw.vertex_array == handle) {
+ draw.vertex_array = 0;
}
+ return *this;
}
-void OpenGLState::ResetFramebuffer(GLuint handle) {
- if (cur_state.draw.read_framebuffer == handle) {
- cur_state.draw.read_framebuffer = 0;
+OpenGLState& OpenGLState::ResetFramebuffer(GLuint handle) {
+ if (draw.read_framebuffer == handle) {
+ draw.read_framebuffer = 0;
}
- if (cur_state.draw.draw_framebuffer == handle) {
- cur_state.draw.draw_framebuffer = 0;
+ if (draw.draw_framebuffer == handle) {
+ draw.draw_framebuffer = 0;
}
+ return *this;
}
diff --git a/src/video_core/renderer_opengl/gl_state.h b/src/video_core/renderer_opengl/gl_state.h
index 437fe34c4..839e50e93 100644
--- a/src/video_core/renderer_opengl/gl_state.h
+++ b/src/video_core/renderer_opengl/gl_state.h
@@ -16,7 +16,7 @@ struct TextureUnit {
}
};
-constexpr TextureUnit PicaTexture(int unit) {
+constexpr TextureUnit MaxwellTexture(int unit) {
return TextureUnit{unit};
}
@@ -85,7 +85,13 @@ public:
struct {
GLuint texture_2d; // GL_TEXTURE_BINDING_2D
GLuint sampler; // GL_SAMPLER_BINDING
- } texture_units[3];
+ struct {
+ GLint r; // GL_TEXTURE_SWIZZLE_R
+ GLint g; // GL_TEXTURE_SWIZZLE_G
+ GLint b; // GL_TEXTURE_SWIZZLE_B
+ GLint a; // GL_TEXTURE_SWIZZLE_A
+ } swizzle;
+ } texture_units[32];
struct {
GLuint texture_buffer; // GL_TEXTURE_BINDING_BUFFER
@@ -122,27 +128,50 @@ public:
GLuint vertex_buffer; // GL_ARRAY_BUFFER_BINDING
GLuint uniform_buffer; // GL_UNIFORM_BUFFER_BINDING
GLuint shader_program; // GL_CURRENT_PROGRAM
+ GLuint program_pipeline; // GL_PROGRAM_PIPELINE_BINDING
+ struct ConstBufferConfig {
+ bool enabled = false;
+ GLuint bindpoint;
+ GLuint ssbo;
+ };
+ std::array<std::array<ConstBufferConfig, 16>, 5> const_buffers{};
} draw;
+ struct {
+ bool enabled; // GL_SCISSOR_TEST
+ GLint x;
+ GLint y;
+ GLsizei width;
+ GLsizei height;
+ } scissor;
+
+ struct {
+ GLint x;
+ GLint y;
+ GLsizei width;
+ GLsizei height;
+ } viewport;
+
std::array<bool, 2> clip_distance; // GL_CLIP_DISTANCE
OpenGLState();
/// Get the currently active OpenGL state
- static const OpenGLState& GetCurState() {
+ static OpenGLState GetCurState() {
return cur_state;
}
/// Apply this state as the current OpenGL state
void Apply() const;
- /// Resets and unbinds any references to the given resource in the current OpenGL state
- static void ResetTexture(GLuint handle);
- static void ResetSampler(GLuint handle);
- static void ResetProgram(GLuint handle);
- static void ResetBuffer(GLuint handle);
- static void ResetVertexArray(GLuint handle);
- static void ResetFramebuffer(GLuint handle);
+ /// Resets any references to the given resource
+ OpenGLState& ResetTexture(GLuint handle);
+ OpenGLState& ResetSampler(GLuint handle);
+ OpenGLState& ResetProgram(GLuint handle);
+ OpenGLState& ResetPipeline(GLuint handle);
+ OpenGLState& ResetBuffer(GLuint handle);
+ OpenGLState& ResetVertexArray(GLuint handle);
+ OpenGLState& ResetFramebuffer(GLuint handle);
private:
static OpenGLState cur_state;
diff --git a/src/video_core/renderer_opengl/gl_stream_buffer.cpp b/src/video_core/renderer_opengl/gl_stream_buffer.cpp
new file mode 100644
index 000000000..a2713e9f0
--- /dev/null
+++ b/src/video_core/renderer_opengl/gl_stream_buffer.cpp
@@ -0,0 +1,182 @@
+// Copyright 2018 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <deque>
+#include <vector>
+#include "common/alignment.h"
+#include "common/assert.h"
+#include "video_core/renderer_opengl/gl_state.h"
+#include "video_core/renderer_opengl/gl_stream_buffer.h"
+
+class OrphanBuffer : public OGLStreamBuffer {
+public:
+ explicit OrphanBuffer(GLenum target) : OGLStreamBuffer(target) {}
+ ~OrphanBuffer() override;
+
+private:
+ void Create(size_t size, size_t sync_subdivide) override;
+ void Release() override;
+
+ std::pair<u8*, GLintptr> Map(size_t size, size_t alignment) override;
+ void Unmap() override;
+
+ std::vector<u8> data;
+};
+
+class StorageBuffer : public OGLStreamBuffer {
+public:
+ explicit StorageBuffer(GLenum target) : OGLStreamBuffer(target) {}
+ ~StorageBuffer() override;
+
+private:
+ void Create(size_t size, size_t sync_subdivide) override;
+ void Release() override;
+
+ std::pair<u8*, GLintptr> Map(size_t size, size_t alignment) override;
+ void Unmap() override;
+
+ struct Fence {
+ OGLSync sync;
+ size_t offset;
+ };
+ std::deque<Fence> head;
+ std::deque<Fence> tail;
+
+ u8* mapped_ptr;
+};
+
+OGLStreamBuffer::OGLStreamBuffer(GLenum target) {
+ gl_target = target;
+}
+
+GLuint OGLStreamBuffer::GetHandle() const {
+ return gl_buffer.handle;
+}
+
+std::unique_ptr<OGLStreamBuffer> OGLStreamBuffer::MakeBuffer(bool storage_buffer, GLenum target) {
+ if (storage_buffer) {
+ return std::make_unique<StorageBuffer>(target);
+ }
+ return std::make_unique<OrphanBuffer>(target);
+}
+
+OrphanBuffer::~OrphanBuffer() {
+ Release();
+}
+
+void OrphanBuffer::Create(size_t size, size_t /*sync_subdivide*/) {
+ buffer_pos = 0;
+ buffer_size = size;
+ data.resize(buffer_size);
+
+ if (gl_buffer.handle == 0) {
+ gl_buffer.Create();
+ glBindBuffer(gl_target, gl_buffer.handle);
+ }
+
+ glBufferData(gl_target, static_cast<GLsizeiptr>(buffer_size), nullptr, GL_STREAM_DRAW);
+}
+
+void OrphanBuffer::Release() {
+ gl_buffer.Release();
+}
+
+std::pair<u8*, GLintptr> OrphanBuffer::Map(size_t size, size_t alignment) {
+ buffer_pos = Common::AlignUp(buffer_pos, alignment);
+
+ if (buffer_pos + size > buffer_size) {
+ Create(std::max(buffer_size, size), 0);
+ }
+
+ mapped_size = size;
+ return std::make_pair(&data[buffer_pos], static_cast<GLintptr>(buffer_pos));
+}
+
+void OrphanBuffer::Unmap() {
+ glBufferSubData(gl_target, static_cast<GLintptr>(buffer_pos),
+ static_cast<GLsizeiptr>(mapped_size), &data[buffer_pos]);
+ buffer_pos += mapped_size;
+}
+
+StorageBuffer::~StorageBuffer() {
+ Release();
+}
+
+void StorageBuffer::Create(size_t size, size_t sync_subdivide) {
+ if (gl_buffer.handle != 0)
+ return;
+
+ buffer_pos = 0;
+ buffer_size = size;
+ buffer_sync_subdivide = std::max<size_t>(sync_subdivide, 1);
+
+ gl_buffer.Create();
+ glBindBuffer(gl_target, gl_buffer.handle);
+
+ glBufferStorage(gl_target, static_cast<GLsizeiptr>(buffer_size), nullptr,
+ GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT);
+ mapped_ptr = reinterpret_cast<u8*>(
+ glMapBufferRange(gl_target, 0, static_cast<GLsizeiptr>(buffer_size),
+ GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_FLUSH_EXPLICIT_BIT));
+}
+
+void StorageBuffer::Release() {
+ if (gl_buffer.handle == 0)
+ return;
+
+ glUnmapBuffer(gl_target);
+
+ gl_buffer.Release();
+ head.clear();
+ tail.clear();
+}
+
+std::pair<u8*, GLintptr> StorageBuffer::Map(size_t size, size_t alignment) {
+ ASSERT(size <= buffer_size);
+
+ OGLSync sync;
+
+ buffer_pos = Common::AlignUp(buffer_pos, alignment);
+ size_t effective_offset = Common::AlignDown(buffer_pos, buffer_sync_subdivide);
+
+ if (!head.empty() &&
+ (effective_offset > head.back().offset || buffer_pos + size > buffer_size)) {
+ ASSERT(head.back().sync.handle == 0);
+ head.back().sync.Create();
+ }
+
+ if (buffer_pos + size > buffer_size) {
+ if (!tail.empty()) {
+ std::swap(sync, tail.back().sync);
+ tail.clear();
+ }
+ std::swap(tail, head);
+ buffer_pos = 0;
+ effective_offset = 0;
+ }
+
+ while (!tail.empty() && buffer_pos + size > tail.front().offset) {
+ std::swap(sync, tail.front().sync);
+ tail.pop_front();
+ }
+
+ if (sync.handle != 0) {
+ glClientWaitSync(sync.handle, GL_SYNC_FLUSH_COMMANDS_BIT, GL_TIMEOUT_IGNORED);
+ sync.Release();
+ }
+
+ if (head.empty() || effective_offset > head.back().offset) {
+ head.emplace_back();
+ head.back().offset = effective_offset;
+ }
+
+ mapped_size = size;
+ return std::make_pair(&mapped_ptr[buffer_pos], static_cast<GLintptr>(buffer_pos));
+}
+
+void StorageBuffer::Unmap() {
+ glFlushMappedBufferRange(gl_target, static_cast<GLintptr>(buffer_pos),
+ static_cast<GLsizeiptr>(mapped_size));
+ buffer_pos += mapped_size;
+}
diff --git a/src/video_core/renderer_opengl/gl_stream_buffer.h b/src/video_core/renderer_opengl/gl_stream_buffer.h
new file mode 100644
index 000000000..e78dc5784
--- /dev/null
+++ b/src/video_core/renderer_opengl/gl_stream_buffer.h
@@ -0,0 +1,36 @@
+// Copyright 2018 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <glad/glad.h>
+#include "common/common_types.h"
+#include "video_core/renderer_opengl/gl_resource_manager.h"
+
+class OGLStreamBuffer : private NonCopyable {
+public:
+ explicit OGLStreamBuffer(GLenum target);
+ virtual ~OGLStreamBuffer() = default;
+
+public:
+ static std::unique_ptr<OGLStreamBuffer> MakeBuffer(bool storage_buffer, GLenum target);
+
+ virtual void Create(size_t size, size_t sync_subdivide) = 0;
+ virtual void Release() {}
+
+ GLuint GetHandle() const;
+
+ virtual std::pair<u8*, GLintptr> Map(size_t size, size_t alignment) = 0;
+ virtual void Unmap() = 0;
+
+protected:
+ OGLBuffer gl_buffer;
+ GLenum gl_target;
+
+ size_t buffer_pos = 0;
+ size_t buffer_size = 0;
+ size_t buffer_sync_subdivide = 0;
+ size_t mapped_size = 0;
+};
diff --git a/src/video_core/renderer_opengl/maxwell_to_gl.h b/src/video_core/renderer_opengl/maxwell_to_gl.h
new file mode 100644
index 000000000..2155fb019
--- /dev/null
+++ b/src/video_core/renderer_opengl/maxwell_to_gl.h
@@ -0,0 +1,204 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <glad/glad.h>
+#include "common/common_types.h"
+#include "common/logging/log.h"
+#include "video_core/engines/maxwell_3d.h"
+
+using GLvec2 = std::array<GLfloat, 2>;
+using GLvec3 = std::array<GLfloat, 3>;
+using GLvec4 = std::array<GLfloat, 4>;
+
+using GLuvec2 = std::array<GLuint, 2>;
+using GLuvec3 = std::array<GLuint, 3>;
+using GLuvec4 = std::array<GLuint, 4>;
+
+namespace MaxwellToGL {
+
+using Maxwell = Tegra::Engines::Maxwell3D::Regs;
+
+inline GLenum VertexType(Maxwell::VertexAttribute attrib) {
+ switch (attrib.type) {
+ case Maxwell::VertexAttribute::Type::UnsignedNorm: {
+
+ switch (attrib.size) {
+ case Maxwell::VertexAttribute::Size::Size_8_8_8_8:
+ return GL_UNSIGNED_BYTE;
+ }
+
+ NGLOG_CRITICAL(Render_OpenGL, "Unimplemented vertex size={}", attrib.SizeString());
+ UNREACHABLE();
+ return {};
+ }
+
+ case Maxwell::VertexAttribute::Type::SignedNorm: {
+
+ switch (attrib.size) {
+ case Maxwell::VertexAttribute::Size::Size_8_8_8_8:
+ return GL_BYTE;
+ }
+
+ NGLOG_CRITICAL(Render_OpenGL, "Unimplemented vertex size={}", attrib.SizeString());
+ UNREACHABLE();
+ return {};
+ }
+
+ case Maxwell::VertexAttribute::Type::Float:
+ return GL_FLOAT;
+ }
+
+ NGLOG_CRITICAL(Render_OpenGL, "Unimplemented vertex type={}", attrib.TypeString());
+ UNREACHABLE();
+ return {};
+}
+
+inline GLenum IndexFormat(Maxwell::IndexFormat index_format) {
+ switch (index_format) {
+ case Maxwell::IndexFormat::UnsignedByte:
+ return GL_UNSIGNED_BYTE;
+ case Maxwell::IndexFormat::UnsignedShort:
+ return GL_UNSIGNED_SHORT;
+ case Maxwell::IndexFormat::UnsignedInt:
+ return GL_UNSIGNED_INT;
+ }
+ NGLOG_CRITICAL(Render_OpenGL, "Unimplemented index_format={}", static_cast<u32>(index_format));
+ UNREACHABLE();
+ return {};
+}
+
+inline GLenum PrimitiveTopology(Maxwell::PrimitiveTopology topology) {
+ switch (topology) {
+ case Maxwell::PrimitiveTopology::Triangles:
+ return GL_TRIANGLES;
+ case Maxwell::PrimitiveTopology::TriangleStrip:
+ return GL_TRIANGLE_STRIP;
+ }
+ NGLOG_CRITICAL(Render_OpenGL, "Unimplemented topology={}", static_cast<u32>(topology));
+ UNREACHABLE();
+ return {};
+}
+
+inline GLenum TextureFilterMode(Tegra::Texture::TextureFilter filter_mode) {
+ switch (filter_mode) {
+ case Tegra::Texture::TextureFilter::Linear:
+ return GL_LINEAR;
+ case Tegra::Texture::TextureFilter::Nearest:
+ return GL_NEAREST;
+ }
+ NGLOG_CRITICAL(Render_OpenGL, "Unimplemented texture filter mode={}",
+ static_cast<u32>(filter_mode));
+ UNREACHABLE();
+ return {};
+}
+
+inline GLenum WrapMode(Tegra::Texture::WrapMode wrap_mode) {
+ switch (wrap_mode) {
+ case Tegra::Texture::WrapMode::Wrap:
+ return GL_REPEAT;
+ case Tegra::Texture::WrapMode::Mirror:
+ return GL_MIRRORED_REPEAT;
+ case Tegra::Texture::WrapMode::ClampToEdge:
+ return GL_CLAMP_TO_EDGE;
+ 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;
+ }
+ NGLOG_CRITICAL(Render_OpenGL, "Unimplemented texture wrap mode={}",
+ static_cast<u32>(wrap_mode));
+ UNREACHABLE();
+ return {};
+}
+
+inline GLenum BlendEquation(Maxwell::Blend::Equation equation) {
+ switch (equation) {
+ case Maxwell::Blend::Equation::Add:
+ return GL_FUNC_ADD;
+ case Maxwell::Blend::Equation::Subtract:
+ return GL_FUNC_SUBTRACT;
+ case Maxwell::Blend::Equation::ReverseSubtract:
+ return GL_FUNC_REVERSE_SUBTRACT;
+ case Maxwell::Blend::Equation::Min:
+ return GL_MIN;
+ case Maxwell::Blend::Equation::Max:
+ return GL_MAX;
+ }
+ NGLOG_CRITICAL(Render_OpenGL, "Unimplemented blend equation={}", static_cast<u32>(equation));
+ UNREACHABLE();
+ return {};
+}
+
+inline GLenum BlendFunc(Maxwell::Blend::Factor factor) {
+ switch (factor) {
+ case Maxwell::Blend::Factor::Zero:
+ return GL_ZERO;
+ case Maxwell::Blend::Factor::One:
+ return GL_ONE;
+ case Maxwell::Blend::Factor::SourceColor:
+ return GL_SRC_COLOR;
+ case Maxwell::Blend::Factor::OneMinusSourceColor:
+ return GL_ONE_MINUS_SRC_COLOR;
+ case Maxwell::Blend::Factor::SourceAlpha:
+ return GL_SRC_ALPHA;
+ case Maxwell::Blend::Factor::OneMinusSourceAlpha:
+ return GL_ONE_MINUS_SRC_ALPHA;
+ case Maxwell::Blend::Factor::DestAlpha:
+ return GL_DST_ALPHA;
+ case Maxwell::Blend::Factor::OneMinusDestAlpha:
+ return GL_ONE_MINUS_DST_ALPHA;
+ case Maxwell::Blend::Factor::DestColor:
+ return GL_DST_COLOR;
+ case Maxwell::Blend::Factor::OneMinusDestColor:
+ return GL_ONE_MINUS_DST_COLOR;
+ case Maxwell::Blend::Factor::SourceAlphaSaturate:
+ return GL_SRC_ALPHA_SATURATE;
+ case Maxwell::Blend::Factor::Source1Color:
+ return GL_SRC1_COLOR;
+ case Maxwell::Blend::Factor::OneMinusSource1Color:
+ return GL_ONE_MINUS_SRC1_COLOR;
+ case Maxwell::Blend::Factor::Source1Alpha:
+ return GL_SRC1_ALPHA;
+ case Maxwell::Blend::Factor::OneMinusSource1Alpha:
+ return GL_ONE_MINUS_SRC1_ALPHA;
+ case Maxwell::Blend::Factor::ConstantColor:
+ return GL_CONSTANT_COLOR;
+ case Maxwell::Blend::Factor::OneMinusConstantColor:
+ return GL_ONE_MINUS_CONSTANT_COLOR;
+ case Maxwell::Blend::Factor::ConstantAlpha:
+ return GL_CONSTANT_ALPHA;
+ case Maxwell::Blend::Factor::OneMinusConstantAlpha:
+ return GL_ONE_MINUS_CONSTANT_ALPHA;
+ }
+ NGLOG_CRITICAL(Render_OpenGL, "Unimplemented blend factor={}", static_cast<u32>(factor));
+ UNREACHABLE();
+ return {};
+}
+
+inline GLenum SwizzleSource(Tegra::Texture::SwizzleSource source) {
+ switch (source) {
+ case Tegra::Texture::SwizzleSource::Zero:
+ return GL_ZERO;
+ case Tegra::Texture::SwizzleSource::R:
+ return GL_RED;
+ case Tegra::Texture::SwizzleSource::G:
+ return GL_GREEN;
+ case Tegra::Texture::SwizzleSource::B:
+ return GL_BLUE;
+ case Tegra::Texture::SwizzleSource::A:
+ return GL_ALPHA;
+ case Tegra::Texture::SwizzleSource::OneInt:
+ case Tegra::Texture::SwizzleSource::OneFloat:
+ return GL_ONE;
+ }
+ NGLOG_CRITICAL(Render_OpenGL, "Unimplemented swizzle source={}", static_cast<u32>(source));
+ UNREACHABLE();
+ return {};
+}
+
+} // namespace MaxwellToGL
diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp
index 7f921fa32..f33766bfd 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.cpp
+++ b/src/video_core/renderer_opengl/renderer_opengl.cpp
@@ -9,17 +9,15 @@
#include <memory>
#include <glad/glad.h>
#include "common/assert.h"
-#include "common/bit_field.h"
#include "common/logging/log.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/frontend/emu_window.h"
-#include "core/hw/hw.h"
-#include "core/hw/lcd.h"
#include "core/memory.h"
#include "core/settings.h"
#include "core/tracer/recorder.h"
#include "video_core/renderer_opengl/renderer_opengl.h"
+#include "video_core/utils.h"
#include "video_core/video_core.h"
static const char vertex_shader[] = R"(
@@ -56,7 +54,7 @@ uniform sampler2D color_texture;
void main() {
// Swap RGBA -> ABGR so we don't have to do this on the CPU. This needs to change if we have to
// support more framebuffer pixel formats.
- color = texture(color_texture, frag_tex_coord).abgr;
+ color = texture(color_texture, frag_tex_coord);
}
)";
@@ -98,197 +96,89 @@ RendererOpenGL::RendererOpenGL() = default;
RendererOpenGL::~RendererOpenGL() = default;
/// Swap buffers (render frame)
-void RendererOpenGL::SwapBuffers(boost::optional<const FramebufferInfo&> framebuffer_info) {
+void RendererOpenGL::SwapBuffers(boost::optional<const Tegra::FramebufferConfig&> framebuffer) {
+ Core::System::GetInstance().perf_stats.EndSystemFrame();
+
// Maintain the rasterizer's state as a priority
OpenGLState prev_state = OpenGLState::GetCurState();
state.Apply();
- if (framebuffer_info != boost::none) {
- // If framebuffer_info is provided, reload it from memory to a texture
- if (screen_info.texture.width != (GLsizei)framebuffer_info->width ||
- screen_info.texture.height != (GLsizei)framebuffer_info->height ||
- screen_info.texture.pixel_format != framebuffer_info->pixel_format) {
+ if (framebuffer != boost::none) {
+ // If framebuffer is provided, reload it from memory to a texture
+ if (screen_info.texture.width != (GLsizei)framebuffer->width ||
+ screen_info.texture.height != (GLsizei)framebuffer->height ||
+ screen_info.texture.pixel_format != framebuffer->pixel_format) {
// Reallocate texture if the framebuffer size has changed.
// This is expected to not happen very often and hence should not be a
// performance problem.
- ConfigureFramebufferTexture(screen_info.texture, *framebuffer_info);
+ ConfigureFramebufferTexture(screen_info.texture, *framebuffer);
}
- LoadFBToScreenInfo(*framebuffer_info, screen_info);
- }
- DrawScreens();
-
- Core::System::GetInstance().perf_stats.EndSystemFrame();
+ // Load the framebuffer from memory, draw it to the screen, and swap buffers
+ LoadFBToScreenInfo(*framebuffer, screen_info);
+ DrawScreen();
+ render_window->SwapBuffers();
+ }
- // Swap buffers
render_window->PollEvents();
- render_window->SwapBuffers();
Core::System::GetInstance().frame_limiter.DoFrameLimiting(CoreTiming::GetGlobalTimeUs());
Core::System::GetInstance().perf_stats.BeginSystemFrame();
+ // Restore the rasterizer state
prev_state.Apply();
RefreshRasterizerSetting();
}
-static inline u32 MortonInterleave128(u32 x, u32 y) {
- // 128x128 Z-Order coordinate from 2D coordinates
- static constexpr u32 xlut[] = {
- 0x0000, 0x0001, 0x0002, 0x0003, 0x0008, 0x0009, 0x000a, 0x000b, 0x0040, 0x0041, 0x0042,
- 0x0043, 0x0048, 0x0049, 0x004a, 0x004b, 0x0800, 0x0801, 0x0802, 0x0803, 0x0808, 0x0809,
- 0x080a, 0x080b, 0x0840, 0x0841, 0x0842, 0x0843, 0x0848, 0x0849, 0x084a, 0x084b, 0x1000,
- 0x1001, 0x1002, 0x1003, 0x1008, 0x1009, 0x100a, 0x100b, 0x1040, 0x1041, 0x1042, 0x1043,
- 0x1048, 0x1049, 0x104a, 0x104b, 0x1800, 0x1801, 0x1802, 0x1803, 0x1808, 0x1809, 0x180a,
- 0x180b, 0x1840, 0x1841, 0x1842, 0x1843, 0x1848, 0x1849, 0x184a, 0x184b, 0x2000, 0x2001,
- 0x2002, 0x2003, 0x2008, 0x2009, 0x200a, 0x200b, 0x2040, 0x2041, 0x2042, 0x2043, 0x2048,
- 0x2049, 0x204a, 0x204b, 0x2800, 0x2801, 0x2802, 0x2803, 0x2808, 0x2809, 0x280a, 0x280b,
- 0x2840, 0x2841, 0x2842, 0x2843, 0x2848, 0x2849, 0x284a, 0x284b, 0x3000, 0x3001, 0x3002,
- 0x3003, 0x3008, 0x3009, 0x300a, 0x300b, 0x3040, 0x3041, 0x3042, 0x3043, 0x3048, 0x3049,
- 0x304a, 0x304b, 0x3800, 0x3801, 0x3802, 0x3803, 0x3808, 0x3809, 0x380a, 0x380b, 0x3840,
- 0x3841, 0x3842, 0x3843, 0x3848, 0x3849, 0x384a, 0x384b, 0x0000, 0x0001, 0x0002, 0x0003,
- 0x0008, 0x0009, 0x000a, 0x000b, 0x0040, 0x0041, 0x0042, 0x0043, 0x0048, 0x0049, 0x004a,
- 0x004b, 0x0800, 0x0801, 0x0802, 0x0803, 0x0808, 0x0809, 0x080a, 0x080b, 0x0840, 0x0841,
- 0x0842, 0x0843, 0x0848, 0x0849, 0x084a, 0x084b, 0x1000, 0x1001, 0x1002, 0x1003, 0x1008,
- 0x1009, 0x100a, 0x100b, 0x1040, 0x1041, 0x1042, 0x1043, 0x1048, 0x1049, 0x104a, 0x104b,
- 0x1800, 0x1801, 0x1802, 0x1803, 0x1808, 0x1809, 0x180a, 0x180b, 0x1840, 0x1841, 0x1842,
- 0x1843, 0x1848, 0x1849, 0x184a, 0x184b, 0x2000, 0x2001, 0x2002, 0x2003, 0x2008, 0x2009,
- 0x200a, 0x200b, 0x2040, 0x2041, 0x2042, 0x2043, 0x2048, 0x2049, 0x204a, 0x204b, 0x2800,
- 0x2801, 0x2802, 0x2803, 0x2808, 0x2809, 0x280a, 0x280b, 0x2840, 0x2841, 0x2842, 0x2843,
- 0x2848, 0x2849, 0x284a, 0x284b, 0x3000, 0x3001, 0x3002, 0x3003, 0x3008, 0x3009, 0x300a,
- 0x300b, 0x3040, 0x3041, 0x3042, 0x3043, 0x3048, 0x3049, 0x304a, 0x304b, 0x3800, 0x3801,
- 0x3802, 0x3803, 0x3808, 0x3809, 0x380a, 0x380b, 0x3840, 0x3841, 0x3842, 0x3843, 0x3848,
- 0x3849, 0x384a, 0x384b, 0x0000, 0x0001, 0x0002, 0x0003, 0x0008, 0x0009, 0x000a, 0x000b,
- 0x0040, 0x0041, 0x0042, 0x0043, 0x0048, 0x0049, 0x004a, 0x004b, 0x0800, 0x0801, 0x0802,
- 0x0803, 0x0808, 0x0809, 0x080a, 0x080b, 0x0840, 0x0841, 0x0842, 0x0843, 0x0848, 0x0849,
- 0x084a, 0x084b, 0x1000, 0x1001, 0x1002, 0x1003, 0x1008, 0x1009, 0x100a, 0x100b, 0x1040,
- 0x1041, 0x1042, 0x1043, 0x1048, 0x1049, 0x104a, 0x104b, 0x1800, 0x1801, 0x1802, 0x1803,
- 0x1808, 0x1809, 0x180a, 0x180b, 0x1840, 0x1841, 0x1842, 0x1843, 0x1848, 0x1849, 0x184a,
- 0x184b, 0x2000, 0x2001, 0x2002, 0x2003, 0x2008, 0x2009, 0x200a, 0x200b, 0x2040, 0x2041,
- 0x2042, 0x2043, 0x2048, 0x2049, 0x204a, 0x204b, 0x2800, 0x2801, 0x2802, 0x2803, 0x2808,
- 0x2809, 0x280a, 0x280b, 0x2840, 0x2841, 0x2842, 0x2843, 0x2848, 0x2849, 0x284a, 0x284b,
- 0x3000, 0x3001, 0x3002, 0x3003, 0x3008, 0x3009, 0x300a, 0x300b, 0x3040, 0x3041, 0x3042,
- 0x3043, 0x3048, 0x3049, 0x304a, 0x304b, 0x3800, 0x3801, 0x3802, 0x3803, 0x3808, 0x3809,
- 0x380a, 0x380b, 0x3840, 0x3841, 0x3842, 0x3843, 0x3848, 0x3849, 0x384a, 0x384b,
- };
- static constexpr u32 ylut[] = {
- 0x0000, 0x0004, 0x0010, 0x0014, 0x0020, 0x0024, 0x0030, 0x0034, 0x0080, 0x0084, 0x0090,
- 0x0094, 0x00a0, 0x00a4, 0x00b0, 0x00b4, 0x0100, 0x0104, 0x0110, 0x0114, 0x0120, 0x0124,
- 0x0130, 0x0134, 0x0180, 0x0184, 0x0190, 0x0194, 0x01a0, 0x01a4, 0x01b0, 0x01b4, 0x0200,
- 0x0204, 0x0210, 0x0214, 0x0220, 0x0224, 0x0230, 0x0234, 0x0280, 0x0284, 0x0290, 0x0294,
- 0x02a0, 0x02a4, 0x02b0, 0x02b4, 0x0300, 0x0304, 0x0310, 0x0314, 0x0320, 0x0324, 0x0330,
- 0x0334, 0x0380, 0x0384, 0x0390, 0x0394, 0x03a0, 0x03a4, 0x03b0, 0x03b4, 0x0400, 0x0404,
- 0x0410, 0x0414, 0x0420, 0x0424, 0x0430, 0x0434, 0x0480, 0x0484, 0x0490, 0x0494, 0x04a0,
- 0x04a4, 0x04b0, 0x04b4, 0x0500, 0x0504, 0x0510, 0x0514, 0x0520, 0x0524, 0x0530, 0x0534,
- 0x0580, 0x0584, 0x0590, 0x0594, 0x05a0, 0x05a4, 0x05b0, 0x05b4, 0x0600, 0x0604, 0x0610,
- 0x0614, 0x0620, 0x0624, 0x0630, 0x0634, 0x0680, 0x0684, 0x0690, 0x0694, 0x06a0, 0x06a4,
- 0x06b0, 0x06b4, 0x0700, 0x0704, 0x0710, 0x0714, 0x0720, 0x0724, 0x0730, 0x0734, 0x0780,
- 0x0784, 0x0790, 0x0794, 0x07a0, 0x07a4, 0x07b0, 0x07b4, 0x0000, 0x0004, 0x0010, 0x0014,
- 0x0020, 0x0024, 0x0030, 0x0034, 0x0080, 0x0084, 0x0090, 0x0094, 0x00a0, 0x00a4, 0x00b0,
- 0x00b4, 0x0100, 0x0104, 0x0110, 0x0114, 0x0120, 0x0124, 0x0130, 0x0134, 0x0180, 0x0184,
- 0x0190, 0x0194, 0x01a0, 0x01a4, 0x01b0, 0x01b4, 0x0200, 0x0204, 0x0210, 0x0214, 0x0220,
- 0x0224, 0x0230, 0x0234, 0x0280, 0x0284, 0x0290, 0x0294, 0x02a0, 0x02a4, 0x02b0, 0x02b4,
- 0x0300, 0x0304, 0x0310, 0x0314, 0x0320, 0x0324, 0x0330, 0x0334, 0x0380, 0x0384, 0x0390,
- 0x0394, 0x03a0, 0x03a4, 0x03b0, 0x03b4, 0x0400, 0x0404, 0x0410, 0x0414, 0x0420, 0x0424,
- 0x0430, 0x0434, 0x0480, 0x0484, 0x0490, 0x0494, 0x04a0, 0x04a4, 0x04b0, 0x04b4, 0x0500,
- 0x0504, 0x0510, 0x0514, 0x0520, 0x0524, 0x0530, 0x0534, 0x0580, 0x0584, 0x0590, 0x0594,
- 0x05a0, 0x05a4, 0x05b0, 0x05b4, 0x0600, 0x0604, 0x0610, 0x0614, 0x0620, 0x0624, 0x0630,
- 0x0634, 0x0680, 0x0684, 0x0690, 0x0694, 0x06a0, 0x06a4, 0x06b0, 0x06b4, 0x0700, 0x0704,
- 0x0710, 0x0714, 0x0720, 0x0724, 0x0730, 0x0734, 0x0780, 0x0784, 0x0790, 0x0794, 0x07a0,
- 0x07a4, 0x07b0, 0x07b4, 0x0000, 0x0004, 0x0010, 0x0014, 0x0020, 0x0024, 0x0030, 0x0034,
- 0x0080, 0x0084, 0x0090, 0x0094, 0x00a0, 0x00a4, 0x00b0, 0x00b4, 0x0100, 0x0104, 0x0110,
- 0x0114, 0x0120, 0x0124, 0x0130, 0x0134, 0x0180, 0x0184, 0x0190, 0x0194, 0x01a0, 0x01a4,
- 0x01b0, 0x01b4, 0x0200, 0x0204, 0x0210, 0x0214, 0x0220, 0x0224, 0x0230, 0x0234, 0x0280,
- 0x0284, 0x0290, 0x0294, 0x02a0, 0x02a4, 0x02b0, 0x02b4, 0x0300, 0x0304, 0x0310, 0x0314,
- 0x0320, 0x0324, 0x0330, 0x0334, 0x0380, 0x0384, 0x0390, 0x0394, 0x03a0, 0x03a4, 0x03b0,
- 0x03b4, 0x0400, 0x0404, 0x0410, 0x0414, 0x0420, 0x0424, 0x0430, 0x0434, 0x0480, 0x0484,
- 0x0490, 0x0494, 0x04a0, 0x04a4, 0x04b0, 0x04b4, 0x0500, 0x0504, 0x0510, 0x0514, 0x0520,
- 0x0524, 0x0530, 0x0534, 0x0580, 0x0584, 0x0590, 0x0594, 0x05a0, 0x05a4, 0x05b0, 0x05b4,
- 0x0600, 0x0604, 0x0610, 0x0614, 0x0620, 0x0624, 0x0630, 0x0634, 0x0680, 0x0684, 0x0690,
- 0x0694, 0x06a0, 0x06a4, 0x06b0, 0x06b4, 0x0700, 0x0704, 0x0710, 0x0714, 0x0720, 0x0724,
- 0x0730, 0x0734, 0x0780, 0x0784, 0x0790, 0x0794, 0x07a0, 0x07a4, 0x07b0, 0x07b4,
- };
- return xlut[x % 128] + ylut[y % 128];
-}
-
-static inline u32 GetMortonOffset128(u32 x, u32 y, u32 bytes_per_pixel) {
- // Calculates the offset of the position of the pixel in Morton order
- // Framebuffer images are split into 128x128 tiles.
-
- const unsigned int block_height = 128;
- const unsigned int coarse_x = x & ~127;
-
- u32 i = MortonInterleave128(x, y);
-
- const unsigned int offset = coarse_x * block_height;
-
- return (i + offset) * bytes_per_pixel;
-}
-
-static void MortonCopyPixels128(u32 width, u32 height, u32 bytes_per_pixel, u32 gl_bytes_per_pixel,
- u8* morton_data, u8* gl_data, bool morton_to_gl) {
- u8* data_ptrs[2];
- for (unsigned y = 0; y < height; ++y) {
- for (unsigned x = 0; x < width; ++x) {
- const u32 coarse_y = y & ~127;
- u32 morton_offset =
- GetMortonOffset128(x, y, bytes_per_pixel) + coarse_y * width * bytes_per_pixel;
- u32 gl_pixel_index = (x + (height - 1 - y) * width) * gl_bytes_per_pixel;
-
- data_ptrs[morton_to_gl] = morton_data + morton_offset;
- data_ptrs[!morton_to_gl] = &gl_data[gl_pixel_index];
-
- memcpy(data_ptrs[0], data_ptrs[1], bytes_per_pixel);
- }
- }
-}
-
/**
* Loads framebuffer from emulated memory into the active OpenGL texture.
*/
-void RendererOpenGL::LoadFBToScreenInfo(const FramebufferInfo& framebuffer_info,
+void RendererOpenGL::LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuffer,
ScreenInfo& screen_info) {
- const u32 bpp{FramebufferInfo::BytesPerPixel(framebuffer_info.pixel_format)};
- const u32 size_in_bytes{framebuffer_info.stride * framebuffer_info.height * bpp};
-
- MortonCopyPixels128(framebuffer_info.width, framebuffer_info.height, bpp, 4,
- Memory::GetPointer(framebuffer_info.address), gl_framebuffer_data.data(),
- true);
+ const u32 bytes_per_pixel{Tegra::FramebufferConfig::BytesPerPixel(framebuffer.pixel_format)};
+ const u64 size_in_bytes{framebuffer.stride * framebuffer.height * bytes_per_pixel};
+ const VAddr framebuffer_addr{framebuffer.address + framebuffer.offset};
- LOG_TRACE(Render_OpenGL, "0x%08x bytes from 0x%llx(%dx%d), fmt %x", size_in_bytes,
- framebuffer_info.address, framebuffer_info.width, framebuffer_info.height,
- (int)framebuffer_info.pixel_format);
+ // Framebuffer orientation handling
+ framebuffer_transform_flags = framebuffer.transform_flags;
// Ensure no bad interactions with GL_UNPACK_ALIGNMENT, which by default
// only allows rows to have a memory alignement of 4.
- ASSERT(framebuffer_info.stride % 4 == 0);
+ ASSERT(framebuffer.stride % 4 == 0);
- framebuffer_flip_vertical = framebuffer_info.flip_vertical;
+ if (!Rasterizer()->AccelerateDisplay(framebuffer, framebuffer_addr, framebuffer.stride,
+ screen_info)) {
+ // Reset the screen info's display texture to its own permanent texture
+ screen_info.display_texture = screen_info.texture.resource.handle;
+ screen_info.display_texcoords = MathUtil::Rectangle<float>(0.f, 0.f, 1.f, 1.f);
- // Reset the screen info's display texture to its own permanent texture
- screen_info.display_texture = screen_info.texture.resource.handle;
- screen_info.display_texcoords = MathUtil::Rectangle<float>(0.f, 0.f, 1.f, 1.f);
+ Memory::RasterizerFlushVirtualRegion(framebuffer_addr, size_in_bytes,
+ Memory::FlushMode::Flush);
- // Memory::RasterizerFlushRegion(framebuffer_info.address, size_in_bytes);
+ VideoCore::MortonCopyPixels128(framebuffer.width, framebuffer.height, bytes_per_pixel, 4,
+ Memory::GetPointer(framebuffer_addr),
+ gl_framebuffer_data.data(), true);
- state.texture_units[0].texture_2d = screen_info.texture.resource.handle;
- state.Apply();
+ state.texture_units[0].texture_2d = screen_info.texture.resource.handle;
+ state.Apply();
- glActiveTexture(GL_TEXTURE0);
- glPixelStorei(GL_UNPACK_ROW_LENGTH, (GLint)framebuffer_info.stride);
+ glActiveTexture(GL_TEXTURE0);
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(framebuffer.stride));
- // Update existing texture
- // TODO: Test what happens on hardware when you change the framebuffer dimensions so that
- // they differ from the LCD resolution.
- // TODO: Applications could theoretically crash Citra here by specifying too large
- // framebuffer sizes. We should make sure that this cannot happen.
- glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, framebuffer_info.width, framebuffer_info.height,
- screen_info.texture.gl_format, screen_info.texture.gl_type,
- gl_framebuffer_data.data());
+ // Update existing texture
+ // TODO: Test what happens on hardware when you change the framebuffer dimensions so that
+ // they differ from the LCD resolution.
+ // TODO: Applications could theoretically crash yuzu here by specifying too large
+ // framebuffer sizes. We should make sure that this cannot happen.
+ glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, framebuffer.width, framebuffer.height,
+ screen_info.texture.gl_format, screen_info.texture.gl_type,
+ gl_framebuffer_data.data());
- glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
- state.texture_units[0].texture_2d = 0;
- state.Apply();
+ state.texture_units[0].texture_2d = 0;
+ state.Apply();
+ }
}
/**
@@ -318,7 +208,7 @@ void RendererOpenGL::InitOpenGLObjects() {
0.0f);
// Link shaders and get variable locations
- shader.Create(vertex_shader, fragment_shader);
+ shader.CreateFromSource(vertex_shader, nullptr, fragment_shader);
state.draw.shader_program = shader.handle;
state.Apply();
uniform_modelview_matrix = glGetUniformLocation(shader.handle, "modelview_matrix");
@@ -372,22 +262,21 @@ void RendererOpenGL::InitOpenGLObjects() {
}
void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture,
- const FramebufferInfo& framebuffer_info) {
+ const Tegra::FramebufferConfig& framebuffer) {
- texture.width = framebuffer_info.width;
- texture.height = framebuffer_info.height;
+ texture.width = framebuffer.width;
+ texture.height = framebuffer.height;
GLint internal_format;
- switch (framebuffer_info.pixel_format) {
- case FramebufferInfo::PixelFormat::ABGR8:
- // Use RGBA8 and swap in the fragment shader
+ switch (framebuffer.pixel_format) {
+ case Tegra::FramebufferConfig::PixelFormat::ABGR8:
internal_format = GL_RGBA;
texture.gl_format = GL_RGBA;
- texture.gl_type = GL_UNSIGNED_INT_8_8_8_8;
+ texture.gl_type = GL_UNSIGNED_INT_8_8_8_8_REV;
gl_framebuffer_data.resize(texture.width * texture.height * 4);
break;
default:
- UNIMPLEMENTED();
+ UNREACHABLE();
}
state.texture_units[0].texture_2d = texture.resource.handle;
@@ -401,20 +290,33 @@ void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture,
state.Apply();
}
-void RendererOpenGL::DrawSingleScreen(const ScreenInfo& screen_info, float x, float y, float w,
- float h) {
+void RendererOpenGL::DrawScreenTriangles(const ScreenInfo& screen_info, float x, float y, float w,
+ float h) {
const auto& texcoords = screen_info.display_texcoords;
- const auto& left = framebuffer_flip_vertical ? texcoords.right : texcoords.left;
- const auto& right = framebuffer_flip_vertical ? texcoords.left : texcoords.right;
+ auto left = texcoords.left;
+ auto right = texcoords.right;
+ if (framebuffer_transform_flags != Tegra::FramebufferConfig::TransformFlags::Unset) {
+ if (framebuffer_transform_flags == Tegra::FramebufferConfig::TransformFlags::FlipV) {
+ // Flip the framebuffer vertically
+ left = texcoords.right;
+ right = texcoords.left;
+ } else {
+ // Other transformations are unsupported
+ NGLOG_CRITICAL(Render_OpenGL, "Unsupported framebuffer_transform_flags={}",
+ static_cast<u32>(framebuffer_transform_flags));
+ UNIMPLEMENTED();
+ }
+ }
std::array<ScreenRectVertex, 4> vertices = {{
- ScreenRectVertex(x, y, texcoords.top, right),
- ScreenRectVertex(x + w, y, texcoords.bottom, right),
- ScreenRectVertex(x, y + h, texcoords.top, left),
- ScreenRectVertex(x + w, y + h, texcoords.bottom, left),
+ ScreenRectVertex(x, y, texcoords.top, left),
+ ScreenRectVertex(x + w, y, texcoords.bottom, left),
+ ScreenRectVertex(x, y + h, texcoords.top, right),
+ ScreenRectVertex(x + w, y + h, texcoords.bottom, right),
}};
state.texture_units[0].texture_2d = screen_info.display_texture;
+ state.texture_units[0].swizzle = {GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA};
state.Apply();
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices.data());
@@ -427,7 +329,7 @@ void RendererOpenGL::DrawSingleScreen(const ScreenInfo& screen_info, float x, fl
/**
* Draws the emulated screens to the emulator window.
*/
-void RendererOpenGL::DrawScreens() {
+void RendererOpenGL::DrawScreen() {
const auto& layout = render_window->GetFramebufferLayout();
const auto& screen = layout.screen;
@@ -443,8 +345,8 @@ void RendererOpenGL::DrawScreens() {
glActiveTexture(GL_TEXTURE0);
glUniform1i(uniform_color_texture, 0);
- DrawSingleScreen(screen_info, (float)screen.left, (float)screen.top, (float)screen.GetWidth(),
- (float)screen.GetHeight());
+ DrawScreenTriangles(screen_info, (float)screen.left, (float)screen.top,
+ (float)screen.GetWidth(), (float)screen.GetHeight());
m_current_frame++;
}
@@ -497,21 +399,22 @@ static const char* GetType(GLenum type) {
static void APIENTRY DebugHandler(GLenum source, GLenum type, GLuint id, GLenum severity,
GLsizei length, const GLchar* message, const void* user_param) {
- Log::Level level;
+ const char format[] = "{} {} {}: {}";
+ const char* const str_source = GetSource(source);
+ const char* const str_type = GetType(type);
+
switch (severity) {
case GL_DEBUG_SEVERITY_HIGH:
- level = Log::Level::Error;
+ NGLOG_ERROR(Render_OpenGL, format, str_source, str_type, id, message);
break;
case GL_DEBUG_SEVERITY_MEDIUM:
- level = Log::Level::Warning;
+ NGLOG_WARNING(Render_OpenGL, format, str_source, str_type, id, message);
break;
case GL_DEBUG_SEVERITY_NOTIFICATION:
case GL_DEBUG_SEVERITY_LOW:
- level = Log::Level::Debug;
+ NGLOG_DEBUG(Render_OpenGL, format, str_source, str_type, id, message);
break;
}
- LOG_GENERIC(Log::Class::Render_OpenGL, level, "%s %s %d: %s", GetSource(source), GetType(type),
- id, message);
}
/// Initialize the renderer
@@ -527,9 +430,9 @@ bool RendererOpenGL::Init() {
const char* gpu_vendor{reinterpret_cast<char const*>(glGetString(GL_VENDOR))};
const char* gpu_model{reinterpret_cast<char const*>(glGetString(GL_RENDERER))};
- LOG_INFO(Render_OpenGL, "GL_VERSION: %s", gl_version);
- LOG_INFO(Render_OpenGL, "GL_VENDOR: %s", gpu_vendor);
- LOG_INFO(Render_OpenGL, "GL_RENDERER: %s", gpu_model);
+ NGLOG_INFO(Render_OpenGL, "GL_VERSION: {}", gl_version);
+ NGLOG_INFO(Render_OpenGL, "GL_VENDOR: {}", gpu_vendor);
+ NGLOG_INFO(Render_OpenGL, "GL_RENDERER: {}", gpu_model);
Core::Telemetry().AddField(Telemetry::FieldType::UserSystem, "GPU_Vendor", gpu_vendor);
Core::Telemetry().AddField(Telemetry::FieldType::UserSystem, "GPU_Model", gpu_model);
diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h
index 05bb3c5cf..2cc6d9a00 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.h
+++ b/src/video_core/renderer_opengl/renderer_opengl.h
@@ -14,17 +14,17 @@
class EmuWindow;
-/// Structure used for storing information about the textures for each 3DS screen
+/// Structure used for storing information about the textures for the Switch screen
struct TextureInfo {
OGLTexture resource;
GLsizei width;
GLsizei height;
GLenum gl_format;
GLenum gl_type;
- RendererBase::FramebufferInfo::PixelFormat pixel_format;
+ Tegra::FramebufferConfig::PixelFormat pixel_format;
};
-/// Structure used for storing information about the display target for each 3DS screen
+/// Structure used for storing information about the display target for the Switch screen
struct ScreenInfo {
GLuint display_texture;
MathUtil::Rectangle<float> display_texcoords;
@@ -37,7 +37,7 @@ public:
~RendererOpenGL() override;
/// Swap buffers (render frame)
- void SwapBuffers(boost::optional<const FramebufferInfo&> framebuffer_info) override;
+ void SwapBuffers(boost::optional<const Tegra::FramebufferConfig&> framebuffer) override;
/**
* Set the emulator window to use for renderer
@@ -53,13 +53,14 @@ public:
private:
void InitOpenGLObjects();
- void ConfigureFramebufferTexture(TextureInfo& texture, const FramebufferInfo& framebuffer_info);
- void DrawScreens();
- void DrawSingleScreen(const ScreenInfo& screen_info, float x, float y, float w, float h);
+ void ConfigureFramebufferTexture(TextureInfo& texture,
+ const Tegra::FramebufferConfig& framebuffer);
+ void DrawScreen();
+ void DrawScreenTriangles(const ScreenInfo& screen_info, float x, float y, float w, float h);
void UpdateFramerate();
// Loads framebuffer from emulated memory into the display information structure
- void LoadFBToScreenInfo(const FramebufferInfo& framebuffer_info, ScreenInfo& screen_info);
+ void LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuffer, ScreenInfo& screen_info);
// Fills active OpenGL texture with the given RGBA color.
void LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b, u8 color_a,
const TextureInfo& texture);
@@ -71,7 +72,7 @@ private:
// OpenGL object IDs
OGLVertexArray vertex_array;
OGLBuffer vertex_buffer;
- OGLShader shader;
+ OGLProgram shader;
/// Display information for Switch screen
ScreenInfo screen_info;
@@ -87,6 +88,6 @@ private:
GLuint attrib_position;
GLuint attrib_tex_coord;
- /// Flips the framebuffer vertically when true
- bool framebuffer_flip_vertical;
+ /// Used for transforming the framebuffer orientation
+ Tegra::FramebufferConfig::TransformFlags framebuffer_transform_flags;
};
diff --git a/src/video_core/textures/decoders.cpp b/src/video_core/textures/decoders.cpp
new file mode 100644
index 000000000..7bf9c4c4b
--- /dev/null
+++ b/src/video_core/textures/decoders.cpp
@@ -0,0 +1,136 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cstring>
+#include "common/assert.h"
+#include "core/memory.h"
+#include "video_core/textures/decoders.h"
+#include "video_core/textures/texture.h"
+
+namespace Tegra {
+namespace Texture {
+
+/**
+ * Calculates the offset of an (x, y) position within a swizzled texture.
+ * Taken from the Tegra X1 TRM.
+ */
+static u32 GetSwizzleOffset(u32 x, u32 y, u32 image_width, u32 bytes_per_pixel, u32 block_height) {
+ u32 image_width_in_gobs = image_width * bytes_per_pixel / 64;
+ u32 GOB_address = 0 + (y / (8 * block_height)) * 512 * block_height * image_width_in_gobs +
+ (x * bytes_per_pixel / 64) * 512 * block_height +
+ (y % (8 * block_height) / 8) * 512;
+ x *= bytes_per_pixel;
+ u32 address = GOB_address + ((x % 64) / 32) * 256 + ((y % 8) / 2) * 64 + ((x % 32) / 16) * 32 +
+ (y % 2) * 16 + (x % 16);
+
+ return address;
+}
+
+void CopySwizzledData(u32 width, u32 height, u32 bytes_per_pixel, u32 out_bytes_per_pixel,
+ u8* swizzled_data, u8* unswizzled_data, bool unswizzle, u32 block_height) {
+ u8* data_ptrs[2];
+ for (unsigned y = 0; y < height; ++y) {
+ for (unsigned x = 0; x < width; ++x) {
+ u32 swizzle_offset = GetSwizzleOffset(x, y, width, bytes_per_pixel, block_height);
+ u32 pixel_index = (x + y * width) * out_bytes_per_pixel;
+
+ data_ptrs[unswizzle] = swizzled_data + swizzle_offset;
+ data_ptrs[!unswizzle] = &unswizzled_data[pixel_index];
+
+ std::memcpy(data_ptrs[0], data_ptrs[1], bytes_per_pixel);
+ }
+ }
+}
+
+u32 BytesPerPixel(TextureFormat format) {
+ switch (format) {
+ case TextureFormat::DXT1:
+ case TextureFormat::DXN1:
+ // In this case a 'pixel' actually refers to a 4x4 tile.
+ return 8;
+ case TextureFormat::DXT23:
+ case TextureFormat::DXT45:
+ // In this case a 'pixel' actually refers to a 4x4 tile.
+ return 16;
+ case TextureFormat::A8R8G8B8:
+ case TextureFormat::A2B10G10R10:
+ case TextureFormat::BF10GF11RF11:
+ return 4;
+ case TextureFormat::A1B5G5R5:
+ case TextureFormat::B5G6R5:
+ return 2;
+ case TextureFormat::R8:
+ return 1;
+ case TextureFormat::R16_G16_B16_A16:
+ return 8;
+ default:
+ UNIMPLEMENTED_MSG("Format not implemented");
+ break;
+ }
+}
+
+std::vector<u8> UnswizzleTexture(VAddr address, TextureFormat format, u32 width, u32 height,
+ u32 block_height) {
+ u8* data = Memory::GetPointer(address);
+ u32 bytes_per_pixel = BytesPerPixel(format);
+
+ std::vector<u8> unswizzled_data(width * height * bytes_per_pixel);
+
+ switch (format) {
+ case TextureFormat::DXT1:
+ case TextureFormat::DXT23:
+ case TextureFormat::DXT45:
+ case TextureFormat::DXN1:
+ // In the DXT and DXN formats, each 4x4 tile is swizzled instead of just individual pixel
+ // values.
+ CopySwizzledData(width / 4, height / 4, bytes_per_pixel, bytes_per_pixel, data,
+ unswizzled_data.data(), true, block_height);
+ break;
+ case TextureFormat::A8R8G8B8:
+ case TextureFormat::A2B10G10R10:
+ case TextureFormat::A1B5G5R5:
+ case TextureFormat::B5G6R5:
+ case TextureFormat::R8:
+ case TextureFormat::R16_G16_B16_A16:
+ case TextureFormat::BF10GF11RF11:
+ CopySwizzledData(width, height, bytes_per_pixel, bytes_per_pixel, data,
+ unswizzled_data.data(), true, block_height);
+ break;
+ default:
+ UNIMPLEMENTED_MSG("Format not implemented");
+ break;
+ }
+
+ return unswizzled_data;
+}
+
+std::vector<u8> DecodeTexture(const std::vector<u8>& texture_data, TextureFormat format, u32 width,
+ u32 height) {
+ std::vector<u8> rgba_data;
+
+ // TODO(Subv): Implement.
+ switch (format) {
+ case TextureFormat::DXT1:
+ case TextureFormat::DXT23:
+ case TextureFormat::DXT45:
+ case TextureFormat::DXN1:
+ case TextureFormat::A8R8G8B8:
+ case TextureFormat::A2B10G10R10:
+ case TextureFormat::A1B5G5R5:
+ case TextureFormat::B5G6R5:
+ case TextureFormat::R8:
+ case TextureFormat::BF10GF11RF11:
+ // TODO(Subv): For the time being just forward the same data without any decoding.
+ rgba_data = texture_data;
+ break;
+ default:
+ UNIMPLEMENTED_MSG("Format not implemented");
+ break;
+ }
+
+ return rgba_data;
+}
+
+} // namespace Texture
+} // namespace Tegra
diff --git a/src/video_core/textures/decoders.h b/src/video_core/textures/decoders.h
new file mode 100644
index 000000000..2562c4b06
--- /dev/null
+++ b/src/video_core/textures/decoders.h
@@ -0,0 +1,31 @@
+// Copyright 2018 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/textures/texture.h"
+
+namespace Tegra {
+namespace Texture {
+
+/**
+ * Unswizzles a swizzled texture without changing its format.
+ */
+std::vector<u8> UnswizzleTexture(VAddr address, TextureFormat format, u32 width, u32 height,
+ u32 block_height = TICEntry::DefaultBlockHeight);
+
+/// Copies texture data from a buffer and performs swizzling/unswizzling as necessary.
+void CopySwizzledData(u32 width, u32 height, u32 bytes_per_pixel, u32 out_bytes_per_pixel,
+ u8* swizzled_data, u8* unswizzled_data, bool unswizzle, u32 block_height);
+
+/**
+ * Decodes an unswizzled texture into a A8R8G8B8 texture.
+ */
+std::vector<u8> DecodeTexture(const std::vector<u8>& texture_data, TextureFormat format, u32 width,
+ u32 height);
+
+} // namespace Texture
+} // namespace Tegra
diff --git a/src/video_core/textures/texture.h b/src/video_core/textures/texture.h
new file mode 100644
index 000000000..a17eaf19d
--- /dev/null
+++ b/src/video_core/textures/texture.h
@@ -0,0 +1,264 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/assert.h"
+#include "common/bit_field.h"
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "video_core/memory_manager.h"
+
+namespace Tegra {
+namespace Texture {
+
+enum class TextureFormat : u32 {
+ R32_G32_B32_A32 = 0x01,
+ R32_G32_B32 = 0x02,
+ R16_G16_B16_A16 = 0x03,
+ R32_G32 = 0x04,
+ R32_B24G8 = 0x05,
+ ETC2_RGB = 0x06,
+ X8B8G8R8 = 0x07,
+ A8R8G8B8 = 0x08,
+ A2B10G10R10 = 0x09,
+ ETC2_RGB_PTA = 0x0a,
+ ETC2_RGBA = 0x0b,
+ R16_G16 = 0x0c,
+ G8R24 = 0x0d,
+ G24R8 = 0x0e,
+ R32 = 0x0f,
+ BC6H_SF16 = 0x10,
+ BC6H_UF16 = 0x11,
+ A4B4G4R4 = 0x12,
+ A5B5G5R1 = 0x13,
+ A1B5G5R5 = 0x14,
+ B5G6R5 = 0x15,
+ B6G5R5 = 0x16,
+ BC7U = 0x17,
+ G8R8 = 0x18,
+ EAC = 0x19,
+ EACX2 = 0x1a,
+ R16 = 0x1b,
+ Y8_VIDEO = 0x1c,
+ R8 = 0x1d,
+ G4R4 = 0x1e,
+ R1 = 0x1f,
+ E5B9G9R9_SHAREDEXP = 0x20,
+ BF10GF11RF11 = 0x21,
+ G8B8G8R8 = 0x22,
+ B8G8R8G8 = 0x23,
+ DXT1 = 0x24,
+ DXT23 = 0x25,
+ DXT45 = 0x26,
+ DXN1 = 0x27,
+ DXN2 = 0x28,
+ Z24S8 = 0x29,
+ X8Z24 = 0x2a,
+ S8Z24 = 0x2b,
+ X4V4Z24__COV4R4V = 0x2c,
+ X4V4Z24__COV8R8V = 0x2d,
+ V8Z24__COV4R12V = 0x2e,
+ ZF32 = 0x2f,
+ ZF32_X24S8 = 0x30,
+ X8Z24_X20V4S8__COV4R4V = 0x31,
+ X8Z24_X20V4S8__COV8R8V = 0x32,
+ ZF32_X20V4X8__COV4R4V = 0x33,
+ ZF32_X20V4X8__COV8R8V = 0x34,
+ ZF32_X20V4S8__COV4R4V = 0x35,
+ ZF32_X20V4S8__COV8R8V = 0x36,
+ X8Z24_X16V8S8__COV4R12V = 0x37,
+ ZF32_X16V8X8__COV4R12V = 0x38,
+ ZF32_X16V8S8__COV4R12V = 0x39,
+ Z16 = 0x3a,
+ V8Z24__COV8R24V = 0x3b,
+ X8Z24_X16V8S8__COV8R24V = 0x3c,
+ ZF32_X16V8X8__COV8R24V = 0x3d,
+ ZF32_X16V8S8__COV8R24V = 0x3e,
+ ASTC_2D_4X4 = 0x40,
+ ASTC_2D_5X5 = 0x41,
+ ASTC_2D_6X6 = 0x42,
+ ASTC_2D_8X8 = 0x44,
+ ASTC_2D_10X10 = 0x45,
+ ASTC_2D_12X12 = 0x46,
+ ASTC_2D_5X4 = 0x50,
+ ASTC_2D_6X5 = 0x51,
+ ASTC_2D_8X6 = 0x52,
+ ASTC_2D_10X8 = 0x53,
+ ASTC_2D_12X10 = 0x54,
+ ASTC_2D_8X5 = 0x55,
+ ASTC_2D_10X5 = 0x56,
+ ASTC_2D_10X6 = 0x57,
+};
+
+enum class TextureType : u32 {
+ Texture1D = 0,
+ Texture2D = 1,
+ Texture3D = 2,
+ TextureCubemap = 3,
+ Texture1DArray = 4,
+ Texture2DArray = 5,
+ Texture1DBuffer = 6,
+ Texture2DNoMipmap = 7,
+ TextureCubeArray = 8,
+};
+
+enum class TICHeaderVersion : u32 {
+ OneDBuffer = 0,
+ PitchColorKey = 1,
+ Pitch = 2,
+ BlockLinear = 3,
+ BlockLinearColorKey = 4,
+};
+
+enum class ComponentType : u32 {
+ SNORM = 1,
+ UNORM = 2,
+ SINT = 3,
+ UINT = 4,
+ SNORM_FORCE_FP16 = 5,
+ UNORM_FORCE_FP16 = 6,
+ FLOAT = 7
+};
+
+enum class SwizzleSource : u32 {
+ Zero = 0,
+
+ R = 2,
+ G = 3,
+ B = 4,
+ A = 5,
+ OneInt = 6,
+ OneFloat = 7,
+};
+
+union TextureHandle {
+ u32 raw;
+ BitField<0, 20, u32> tic_id;
+ BitField<20, 12, u32> tsc_id;
+};
+static_assert(sizeof(TextureHandle) == 4, "TextureHandle has wrong size");
+
+struct TICEntry {
+ static constexpr u32 DefaultBlockHeight = 16;
+
+ union {
+ u32 raw;
+ BitField<0, 7, TextureFormat> format;
+ BitField<7, 3, ComponentType> r_type;
+ BitField<10, 3, ComponentType> g_type;
+ BitField<13, 3, ComponentType> b_type;
+ BitField<16, 3, ComponentType> a_type;
+
+ BitField<19, 3, SwizzleSource> x_source;
+ BitField<22, 3, SwizzleSource> y_source;
+ BitField<25, 3, SwizzleSource> z_source;
+ BitField<28, 3, SwizzleSource> w_source;
+ };
+ u32 address_low;
+ union {
+ BitField<0, 16, u32> address_high;
+ BitField<21, 3, TICHeaderVersion> header_version;
+ };
+ union {
+ BitField<3, 3, u32> block_height;
+
+ // High 16 bits of the pitch value
+ BitField<0, 16, u32> pitch_high;
+ };
+ union {
+ BitField<0, 16, u32> width_minus_1;
+ BitField<23, 4, TextureType> texture_type;
+ };
+ u16 height_minus_1;
+ INSERT_PADDING_BYTES(10);
+
+ GPUVAddr Address() const {
+ return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) | address_low);
+ }
+
+ u32 Pitch() const {
+ ASSERT(header_version == TICHeaderVersion::Pitch ||
+ header_version == TICHeaderVersion::PitchColorKey);
+ // The pitch value is 21 bits, and is 32B aligned.
+ return pitch_high << 5;
+ }
+
+ u32 Width() const {
+ return width_minus_1 + 1;
+ }
+
+ u32 Height() const {
+ return height_minus_1 + 1;
+ }
+
+ u32 BlockHeight() const {
+ ASSERT(header_version == TICHeaderVersion::BlockLinear ||
+ header_version == TICHeaderVersion::BlockLinearColorKey);
+ // The block height is stored in log2 format.
+ return 1 << block_height;
+ }
+
+ bool IsTiled() const {
+ return header_version == TICHeaderVersion::BlockLinear ||
+ header_version == TICHeaderVersion::BlockLinearColorKey;
+ }
+};
+static_assert(sizeof(TICEntry) == 0x20, "TICEntry has wrong size");
+
+enum class WrapMode : u32 {
+ Wrap = 0,
+ Mirror = 1,
+ ClampToEdge = 2,
+ Border = 3,
+ ClampOGL = 4,
+ MirrorOnceClampToEdge = 5,
+ MirrorOnceBorder = 6,
+ MirrorOnceClampOGL = 7,
+};
+
+enum class TextureFilter : u32 {
+ Nearest = 1,
+ Linear = 2,
+};
+
+enum class TextureMipmapFilter : u32 {
+ None = 1,
+ Nearest = 2,
+ Linear = 3,
+};
+
+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, u32> depth_compare_func;
+ };
+ union {
+ BitField<0, 2, TextureFilter> mag_filter;
+ BitField<4, 2, TextureFilter> min_filter;
+ BitField<6, 2, TextureMipmapFilter> mip_filter;
+ };
+ INSERT_PADDING_BYTES(8);
+ u32 border_color_r;
+ u32 border_color_g;
+ u32 border_color_b;
+ u32 border_color_a;
+};
+static_assert(sizeof(TSCEntry) == 0x20, "TSCEntry has wrong size");
+
+struct FullTextureInfo {
+ u32 index;
+ TICEntry tic;
+ TSCEntry tsc;
+ bool enabled;
+};
+
+/// Returns the number of bytes per pixel of the input texture format.
+u32 BytesPerPixel(TextureFormat format);
+
+} // namespace Texture
+} // namespace Tegra
diff --git a/src/video_core/utils.h b/src/video_core/utils.h
index d94a10417..e0a14d48f 100644
--- a/src/video_core/utils.h
+++ b/src/video_core/utils.h
@@ -49,4 +49,116 @@ static inline u32 GetMortonOffset(u32 x, u32 y, u32 bytes_per_pixel) {
return (i + offset) * bytes_per_pixel;
}
+static inline u32 MortonInterleave128(u32 x, u32 y) {
+ // 128x128 Z-Order coordinate from 2D coordinates
+ static constexpr u32 xlut[] = {
+ 0x0000, 0x0001, 0x0002, 0x0003, 0x0008, 0x0009, 0x000a, 0x000b, 0x0040, 0x0041, 0x0042,
+ 0x0043, 0x0048, 0x0049, 0x004a, 0x004b, 0x0800, 0x0801, 0x0802, 0x0803, 0x0808, 0x0809,
+ 0x080a, 0x080b, 0x0840, 0x0841, 0x0842, 0x0843, 0x0848, 0x0849, 0x084a, 0x084b, 0x1000,
+ 0x1001, 0x1002, 0x1003, 0x1008, 0x1009, 0x100a, 0x100b, 0x1040, 0x1041, 0x1042, 0x1043,
+ 0x1048, 0x1049, 0x104a, 0x104b, 0x1800, 0x1801, 0x1802, 0x1803, 0x1808, 0x1809, 0x180a,
+ 0x180b, 0x1840, 0x1841, 0x1842, 0x1843, 0x1848, 0x1849, 0x184a, 0x184b, 0x2000, 0x2001,
+ 0x2002, 0x2003, 0x2008, 0x2009, 0x200a, 0x200b, 0x2040, 0x2041, 0x2042, 0x2043, 0x2048,
+ 0x2049, 0x204a, 0x204b, 0x2800, 0x2801, 0x2802, 0x2803, 0x2808, 0x2809, 0x280a, 0x280b,
+ 0x2840, 0x2841, 0x2842, 0x2843, 0x2848, 0x2849, 0x284a, 0x284b, 0x3000, 0x3001, 0x3002,
+ 0x3003, 0x3008, 0x3009, 0x300a, 0x300b, 0x3040, 0x3041, 0x3042, 0x3043, 0x3048, 0x3049,
+ 0x304a, 0x304b, 0x3800, 0x3801, 0x3802, 0x3803, 0x3808, 0x3809, 0x380a, 0x380b, 0x3840,
+ 0x3841, 0x3842, 0x3843, 0x3848, 0x3849, 0x384a, 0x384b, 0x0000, 0x0001, 0x0002, 0x0003,
+ 0x0008, 0x0009, 0x000a, 0x000b, 0x0040, 0x0041, 0x0042, 0x0043, 0x0048, 0x0049, 0x004a,
+ 0x004b, 0x0800, 0x0801, 0x0802, 0x0803, 0x0808, 0x0809, 0x080a, 0x080b, 0x0840, 0x0841,
+ 0x0842, 0x0843, 0x0848, 0x0849, 0x084a, 0x084b, 0x1000, 0x1001, 0x1002, 0x1003, 0x1008,
+ 0x1009, 0x100a, 0x100b, 0x1040, 0x1041, 0x1042, 0x1043, 0x1048, 0x1049, 0x104a, 0x104b,
+ 0x1800, 0x1801, 0x1802, 0x1803, 0x1808, 0x1809, 0x180a, 0x180b, 0x1840, 0x1841, 0x1842,
+ 0x1843, 0x1848, 0x1849, 0x184a, 0x184b, 0x2000, 0x2001, 0x2002, 0x2003, 0x2008, 0x2009,
+ 0x200a, 0x200b, 0x2040, 0x2041, 0x2042, 0x2043, 0x2048, 0x2049, 0x204a, 0x204b, 0x2800,
+ 0x2801, 0x2802, 0x2803, 0x2808, 0x2809, 0x280a, 0x280b, 0x2840, 0x2841, 0x2842, 0x2843,
+ 0x2848, 0x2849, 0x284a, 0x284b, 0x3000, 0x3001, 0x3002, 0x3003, 0x3008, 0x3009, 0x300a,
+ 0x300b, 0x3040, 0x3041, 0x3042, 0x3043, 0x3048, 0x3049, 0x304a, 0x304b, 0x3800, 0x3801,
+ 0x3802, 0x3803, 0x3808, 0x3809, 0x380a, 0x380b, 0x3840, 0x3841, 0x3842, 0x3843, 0x3848,
+ 0x3849, 0x384a, 0x384b, 0x0000, 0x0001, 0x0002, 0x0003, 0x0008, 0x0009, 0x000a, 0x000b,
+ 0x0040, 0x0041, 0x0042, 0x0043, 0x0048, 0x0049, 0x004a, 0x004b, 0x0800, 0x0801, 0x0802,
+ 0x0803, 0x0808, 0x0809, 0x080a, 0x080b, 0x0840, 0x0841, 0x0842, 0x0843, 0x0848, 0x0849,
+ 0x084a, 0x084b, 0x1000, 0x1001, 0x1002, 0x1003, 0x1008, 0x1009, 0x100a, 0x100b, 0x1040,
+ 0x1041, 0x1042, 0x1043, 0x1048, 0x1049, 0x104a, 0x104b, 0x1800, 0x1801, 0x1802, 0x1803,
+ 0x1808, 0x1809, 0x180a, 0x180b, 0x1840, 0x1841, 0x1842, 0x1843, 0x1848, 0x1849, 0x184a,
+ 0x184b, 0x2000, 0x2001, 0x2002, 0x2003, 0x2008, 0x2009, 0x200a, 0x200b, 0x2040, 0x2041,
+ 0x2042, 0x2043, 0x2048, 0x2049, 0x204a, 0x204b, 0x2800, 0x2801, 0x2802, 0x2803, 0x2808,
+ 0x2809, 0x280a, 0x280b, 0x2840, 0x2841, 0x2842, 0x2843, 0x2848, 0x2849, 0x284a, 0x284b,
+ 0x3000, 0x3001, 0x3002, 0x3003, 0x3008, 0x3009, 0x300a, 0x300b, 0x3040, 0x3041, 0x3042,
+ 0x3043, 0x3048, 0x3049, 0x304a, 0x304b, 0x3800, 0x3801, 0x3802, 0x3803, 0x3808, 0x3809,
+ 0x380a, 0x380b, 0x3840, 0x3841, 0x3842, 0x3843, 0x3848, 0x3849, 0x384a, 0x384b,
+ };
+ static constexpr u32 ylut[] = {
+ 0x0000, 0x0004, 0x0010, 0x0014, 0x0020, 0x0024, 0x0030, 0x0034, 0x0080, 0x0084, 0x0090,
+ 0x0094, 0x00a0, 0x00a4, 0x00b0, 0x00b4, 0x0100, 0x0104, 0x0110, 0x0114, 0x0120, 0x0124,
+ 0x0130, 0x0134, 0x0180, 0x0184, 0x0190, 0x0194, 0x01a0, 0x01a4, 0x01b0, 0x01b4, 0x0200,
+ 0x0204, 0x0210, 0x0214, 0x0220, 0x0224, 0x0230, 0x0234, 0x0280, 0x0284, 0x0290, 0x0294,
+ 0x02a0, 0x02a4, 0x02b0, 0x02b4, 0x0300, 0x0304, 0x0310, 0x0314, 0x0320, 0x0324, 0x0330,
+ 0x0334, 0x0380, 0x0384, 0x0390, 0x0394, 0x03a0, 0x03a4, 0x03b0, 0x03b4, 0x0400, 0x0404,
+ 0x0410, 0x0414, 0x0420, 0x0424, 0x0430, 0x0434, 0x0480, 0x0484, 0x0490, 0x0494, 0x04a0,
+ 0x04a4, 0x04b0, 0x04b4, 0x0500, 0x0504, 0x0510, 0x0514, 0x0520, 0x0524, 0x0530, 0x0534,
+ 0x0580, 0x0584, 0x0590, 0x0594, 0x05a0, 0x05a4, 0x05b0, 0x05b4, 0x0600, 0x0604, 0x0610,
+ 0x0614, 0x0620, 0x0624, 0x0630, 0x0634, 0x0680, 0x0684, 0x0690, 0x0694, 0x06a0, 0x06a4,
+ 0x06b0, 0x06b4, 0x0700, 0x0704, 0x0710, 0x0714, 0x0720, 0x0724, 0x0730, 0x0734, 0x0780,
+ 0x0784, 0x0790, 0x0794, 0x07a0, 0x07a4, 0x07b0, 0x07b4, 0x0000, 0x0004, 0x0010, 0x0014,
+ 0x0020, 0x0024, 0x0030, 0x0034, 0x0080, 0x0084, 0x0090, 0x0094, 0x00a0, 0x00a4, 0x00b0,
+ 0x00b4, 0x0100, 0x0104, 0x0110, 0x0114, 0x0120, 0x0124, 0x0130, 0x0134, 0x0180, 0x0184,
+ 0x0190, 0x0194, 0x01a0, 0x01a4, 0x01b0, 0x01b4, 0x0200, 0x0204, 0x0210, 0x0214, 0x0220,
+ 0x0224, 0x0230, 0x0234, 0x0280, 0x0284, 0x0290, 0x0294, 0x02a0, 0x02a4, 0x02b0, 0x02b4,
+ 0x0300, 0x0304, 0x0310, 0x0314, 0x0320, 0x0324, 0x0330, 0x0334, 0x0380, 0x0384, 0x0390,
+ 0x0394, 0x03a0, 0x03a4, 0x03b0, 0x03b4, 0x0400, 0x0404, 0x0410, 0x0414, 0x0420, 0x0424,
+ 0x0430, 0x0434, 0x0480, 0x0484, 0x0490, 0x0494, 0x04a0, 0x04a4, 0x04b0, 0x04b4, 0x0500,
+ 0x0504, 0x0510, 0x0514, 0x0520, 0x0524, 0x0530, 0x0534, 0x0580, 0x0584, 0x0590, 0x0594,
+ 0x05a0, 0x05a4, 0x05b0, 0x05b4, 0x0600, 0x0604, 0x0610, 0x0614, 0x0620, 0x0624, 0x0630,
+ 0x0634, 0x0680, 0x0684, 0x0690, 0x0694, 0x06a0, 0x06a4, 0x06b0, 0x06b4, 0x0700, 0x0704,
+ 0x0710, 0x0714, 0x0720, 0x0724, 0x0730, 0x0734, 0x0780, 0x0784, 0x0790, 0x0794, 0x07a0,
+ 0x07a4, 0x07b0, 0x07b4, 0x0000, 0x0004, 0x0010, 0x0014, 0x0020, 0x0024, 0x0030, 0x0034,
+ 0x0080, 0x0084, 0x0090, 0x0094, 0x00a0, 0x00a4, 0x00b0, 0x00b4, 0x0100, 0x0104, 0x0110,
+ 0x0114, 0x0120, 0x0124, 0x0130, 0x0134, 0x0180, 0x0184, 0x0190, 0x0194, 0x01a0, 0x01a4,
+ 0x01b0, 0x01b4, 0x0200, 0x0204, 0x0210, 0x0214, 0x0220, 0x0224, 0x0230, 0x0234, 0x0280,
+ 0x0284, 0x0290, 0x0294, 0x02a0, 0x02a4, 0x02b0, 0x02b4, 0x0300, 0x0304, 0x0310, 0x0314,
+ 0x0320, 0x0324, 0x0330, 0x0334, 0x0380, 0x0384, 0x0390, 0x0394, 0x03a0, 0x03a4, 0x03b0,
+ 0x03b4, 0x0400, 0x0404, 0x0410, 0x0414, 0x0420, 0x0424, 0x0430, 0x0434, 0x0480, 0x0484,
+ 0x0490, 0x0494, 0x04a0, 0x04a4, 0x04b0, 0x04b4, 0x0500, 0x0504, 0x0510, 0x0514, 0x0520,
+ 0x0524, 0x0530, 0x0534, 0x0580, 0x0584, 0x0590, 0x0594, 0x05a0, 0x05a4, 0x05b0, 0x05b4,
+ 0x0600, 0x0604, 0x0610, 0x0614, 0x0620, 0x0624, 0x0630, 0x0634, 0x0680, 0x0684, 0x0690,
+ 0x0694, 0x06a0, 0x06a4, 0x06b0, 0x06b4, 0x0700, 0x0704, 0x0710, 0x0714, 0x0720, 0x0724,
+ 0x0730, 0x0734, 0x0780, 0x0784, 0x0790, 0x0794, 0x07a0, 0x07a4, 0x07b0, 0x07b4,
+ };
+ return xlut[x % 128] + ylut[y % 128];
+}
+
+static inline u32 GetMortonOffset128(u32 x, u32 y, u32 bytes_per_pixel) {
+ // Calculates the offset of the position of the pixel in Morton order
+ // Framebuffer images are split into 128x128 tiles.
+
+ const unsigned int block_height = 128;
+ const unsigned int coarse_x = x & ~127;
+
+ u32 i = MortonInterleave128(x, y);
+
+ const unsigned int offset = coarse_x * block_height;
+
+ return (i + offset) * bytes_per_pixel;
+}
+
+static inline void MortonCopyPixels128(u32 width, u32 height, u32 bytes_per_pixel,
+ u32 gl_bytes_per_pixel, u8* morton_data, u8* gl_data,
+ bool morton_to_gl) {
+ u8* data_ptrs[2];
+ for (unsigned y = 0; y < height; ++y) {
+ for (unsigned x = 0; x < width; ++x) {
+ const u32 coarse_y = y & ~127;
+ u32 morton_offset =
+ GetMortonOffset128(x, y, bytes_per_pixel) + coarse_y * width * bytes_per_pixel;
+ u32 gl_pixel_index = (x + y * width) * gl_bytes_per_pixel;
+
+ data_ptrs[morton_to_gl] = morton_data + morton_offset;
+ data_ptrs[!morton_to_gl] = &gl_data[gl_pixel_index];
+
+ memcpy(data_ptrs[0], data_ptrs[1], bytes_per_pixel);
+ }
+ }
+}
+
} // namespace VideoCore
diff --git a/src/video_core/video_core.cpp b/src/video_core/video_core.cpp
index 864691baa..89dc8ed1e 100644
--- a/src/video_core/video_core.cpp
+++ b/src/video_core/video_core.cpp
@@ -24,9 +24,9 @@ bool Init(EmuWindow* emu_window) {
g_renderer = std::make_unique<RendererOpenGL>();
g_renderer->SetWindow(g_emu_window);
if (g_renderer->Init()) {
- LOG_DEBUG(Render, "initialized OK");
+ NGLOG_DEBUG(Render, "initialized OK");
} else {
- LOG_ERROR(Render, "initialization failed !");
+ NGLOG_CRITICAL(Render, "initialization failed !");
return false;
}
return true;
@@ -36,7 +36,7 @@ bool Init(EmuWindow* emu_window) {
void Shutdown() {
g_renderer.reset();
- LOG_DEBUG(Render, "shutdown OK");
+ NGLOG_DEBUG(Render, "shutdown OK");
}
} // namespace VideoCore
diff --git a/src/video_core/video_core.h b/src/video_core/video_core.h
index 1fd90b9d0..37da62436 100644
--- a/src/video_core/video_core.h
+++ b/src/video_core/video_core.h
@@ -15,6 +15,8 @@ class RendererBase;
namespace VideoCore {
+enum class Renderer { Software, OpenGL };
+
extern std::unique_ptr<RendererBase> g_renderer; ///< Renderer plugin
extern EmuWindow* g_emu_window; ///< Emu window
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 0c4056c49..c662570d2 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -23,10 +23,15 @@ add_executable(yuzu
configuration/configure_input.h
configuration/configure_system.cpp
configuration/configure_system.h
+ debugger/graphics/graphics_breakpoint_observer.cpp
+ debugger/graphics/graphics_breakpoint_observer.h
+ 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/profiler.cpp
debugger/profiler.h
- debugger/registers.cpp
- debugger/registers.h
debugger/wait_tree.cpp
debugger/wait_tree.h
game_list.cpp
@@ -53,7 +58,6 @@ set(UIS
configuration/configure_graphics.ui
configuration/configure_input.ui
configuration/configure_system.ui
- debugger/registers.ui
hotkeys.ui
main.ui
)
diff --git a/src/yuzu/about_dialog.cpp b/src/yuzu/about_dialog.cpp
index da3429822..d6647eeea 100644
--- a/src/yuzu/about_dialog.cpp
+++ b/src/yuzu/about_dialog.cpp
@@ -2,12 +2,14 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <QIcon>
#include "common/scm_rev.h"
#include "ui_aboutdialog.h"
#include "yuzu/about_dialog.h"
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_name, Common::g_scm_branch, Common::g_scm_desc));
}
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp
index 469988d63..5c17cd0d9 100644
--- a/src/yuzu/bootmanager.cpp
+++ b/src/yuzu/bootmanager.cpp
@@ -4,6 +4,8 @@
#include <QScreen>
#include <QWindow>
+#include <fmt/format.h>
+
#include "common/microprofile.h"
#include "common/scm_rev.h"
#include "common/string_util.h"
@@ -102,8 +104,8 @@ private:
GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread)
: QWidget(parent), child(nullptr), emu_thread(emu_thread) {
- std::string window_title = Common::StringFromFormat("yuzu %s| %s-%s", Common::g_build_name,
- Common::g_scm_branch, Common::g_scm_desc);
+ std::string window_title = fmt::format("yuzu {} | {}-{}", Common::g_build_name,
+ Common::g_scm_branch, Common::g_scm_desc);
setWindowTitle(QString::fromStdString(window_title));
InputCommon::Init();
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index f9ddb9edc..8316db708 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -77,8 +77,8 @@ void Config::ReadValues() {
qt_config->endGroup();
qt_config->beginGroup("Core");
- Settings::values.cpu_core =
- static_cast<Settings::CpuCore>(qt_config->value("cpu_core", 0).toInt());
+ Settings::values.use_cpu_jit = qt_config->value("use_cpu_jit", true).toBool();
+ Settings::values.use_multi_core = qt_config->value("use_multi_core", false).toBool();
qt_config->endGroup();
qt_config->beginGroup("Renderer");
@@ -94,6 +94,10 @@ void Config::ReadValues() {
Settings::values.use_virtual_sd = qt_config->value("use_virtual_sd", true).toBool();
qt_config->endGroup();
+ qt_config->beginGroup("System");
+ Settings::values.use_docked_mode = qt_config->value("use_docked_mode", true).toBool();
+ qt_config->endGroup();
+
qt_config->beginGroup("Miscellaneous");
Settings::values.log_filter = qt_config->value("log_filter", "*:Info").toString().toStdString();
qt_config->endGroup();
@@ -104,6 +108,8 @@ void Config::ReadValues() {
qt_config->endGroup();
qt_config->beginGroup("UI");
+ UISettings::values.theme = qt_config->value("theme", UISettings::themes[0].second).toString();
+
qt_config->beginGroup("UILayout");
UISettings::values.geometry = qt_config->value("geometry").toByteArray();
UISettings::values.state = qt_config->value("state").toByteArray();
@@ -171,7 +177,8 @@ void Config::SaveValues() {
qt_config->endGroup();
qt_config->beginGroup("Core");
- qt_config->setValue("cpu_core", static_cast<int>(Settings::values.cpu_core));
+ qt_config->setValue("use_cpu_jit", Settings::values.use_cpu_jit);
+ qt_config->setValue("use_multi_core", Settings::values.use_multi_core);
qt_config->endGroup();
qt_config->beginGroup("Renderer");
@@ -188,6 +195,10 @@ void Config::SaveValues() {
qt_config->setValue("use_virtual_sd", Settings::values.use_virtual_sd);
qt_config->endGroup();
+ qt_config->beginGroup("System");
+ qt_config->setValue("use_docked_mode", Settings::values.use_docked_mode);
+ qt_config->endGroup();
+
qt_config->beginGroup("Miscellaneous");
qt_config->setValue("log_filter", QString::fromStdString(Settings::values.log_filter));
qt_config->endGroup();
diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp
index 92fd6ab02..baa558667 100644
--- a/src/yuzu/configuration/configure_general.cpp
+++ b/src/yuzu/configuration/configure_general.cpp
@@ -13,9 +13,15 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent)
ui->setupUi(this);
+ for (auto theme : UISettings::themes) {
+ ui->theme_combobox->addItem(theme.first, theme.second);
+ }
+
this->setConfiguration();
- ui->cpu_core_combobox->setEnabled(!Core::System::GetInstance().IsPoweredOn());
+ ui->use_cpu_jit->setEnabled(!Core::System::GetInstance().IsPoweredOn());
+ ui->use_multi_core->setEnabled(!Core::System::GetInstance().IsPoweredOn());
+ ui->use_docked_mode->setEnabled(!Core::System::GetInstance().IsPoweredOn());
}
ConfigureGeneral::~ConfigureGeneral() {}
@@ -23,13 +29,20 @@ ConfigureGeneral::~ConfigureGeneral() {}
void ConfigureGeneral::setConfiguration() {
ui->toggle_deepscan->setChecked(UISettings::values.gamedir_deepscan);
ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing);
- ui->cpu_core_combobox->setCurrentIndex(static_cast<int>(Settings::values.cpu_core));
+ ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme));
+ ui->use_cpu_jit->setChecked(Settings::values.use_cpu_jit);
+ ui->use_multi_core->setChecked(Settings::values.use_multi_core);
+ ui->use_docked_mode->setChecked(Settings::values.use_docked_mode);
}
void ConfigureGeneral::applyConfiguration() {
UISettings::values.gamedir_deepscan = ui->toggle_deepscan->isChecked();
UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked();
- Settings::values.cpu_core =
- static_cast<Settings::CpuCore>(ui->cpu_core_combobox->currentIndex());
+ UISettings::values.theme =
+ ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString();
+
+ Settings::values.use_cpu_jit = ui->use_cpu_jit->isChecked();
+ Settings::values.use_multi_core = ui->use_multi_core->isChecked();
+ Settings::values.use_docked_mode = ui->use_docked_mode->isChecked();
Settings::Apply();
}
diff --git a/src/yuzu/configuration/configure_general.ui b/src/yuzu/configuration/configure_general.ui
index 573c4cb0e..233adbe27 100644
--- a/src/yuzu/configuration/configure_general.ui
+++ b/src/yuzu/configuration/configure_general.ui
@@ -13,17 +13,17 @@
<property name="windowTitle">
<string>Form</string>
</property>
- <layout class="QHBoxLayout" name="horizontalLayout">
+ <layout class="QHBoxLayout" name="HorizontalLayout">
<item>
- <layout class="QVBoxLayout" name="verticalLayout">
+ <layout class="QVBoxLayout" name="VerticalLayout">
<item>
- <widget class="QGroupBox" name="groupBox">
+ <widget class="QGroupBox" name="GeneralGroupBox">
<property name="title">
<string>General</string>
</property>
- <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <layout class="QHBoxLayout" name="GeneralHorizontalLayout">
<item>
- <layout class="QVBoxLayout" name="verticalLayout_2">
+ <layout class="QVBoxLayout" name="GeneralVerticalLayout">
<item>
<widget class="QCheckBox" name="toggle_deepscan">
<property name="text">
@@ -44,40 +44,87 @@
</widget>
</item>
<item>
- <widget class="QGroupBox" name="groupBox_2">
- <property name="title">
- <string>CPU Core</string>
- </property>
- <layout class="QHBoxLayout" name="horizontalLayout_7">
- <item>
- <layout class="QVBoxLayout" name="verticalLayout_5">
- <item>
- <widget class="QComboBox" name="cpu_core_combobox">
- <item>
- <property name="text">
- <string>Unicorn</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Dynarmic</string>
- </property>
- </item>
- </widget>
- </item>
- </layout>
- </item>
+ <widget class="QGroupBox" name="PerformanceGroupBox">
+ <property name="title">
+ <string>Performance</string>
+ </property>
+ <layout class="QHBoxLayout" name="PerformanceHorizontalLayout">
+ <item>
+ <layout class="QVBoxLayout" name="PerformanceVerticalLayout">
+ <item>
+ <widget class="QCheckBox" name="use_cpu_jit">
+ <property name="text">
+ <string>Enable CPU JIT</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="use_multi_core">
+ <property name="text">
+ <string>Enable multi-core</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </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="use_docked_mode">
+ <property name="text">
+ <string>Enable docked mode</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="theme_group_box">
+ <property name="title">
+ <string>Theme</string>
+ </property>
+ <layout class="QHBoxLayout" name="theme_qhbox_layout">
+ <item>
+ <layout class="QVBoxLayout" name="theme_qvbox_layout">
+ <item>
+ <layout class="QHBoxLayout" name="theme_qhbox_layout_2">
+ <item>
+ <widget class="QLabel" name="theme_label">
+ <property name="text">
+ <string>Theme:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="theme_combobox"/>
+ </item>
+ </layout>
+ </item>
</layout>
- </widget>
+ </item>
+ </layout>
+ </widget>
</item>
<item>
- <widget class="QGroupBox" name="groupBox_3">
+ <widget class="QGroupBox" name="HotKeysGroupBox">
<property name="title">
<string>Hotkeys</string>
</property>
- <layout class="QHBoxLayout" name="horizontalLayout_4">
+ <layout class="QHBoxLayout" name="HotKeysHorizontalLayout">
<item>
- <layout class="QVBoxLayout" name="verticalLayout_4">
+ <layout class="QVBoxLayout" name="HotKeysVerticalLayout">
<item>
<widget class="GHotkeysDialog" name="widget" native="true"/>
</item>
diff --git a/src/yuzu/debugger/graphics/graphics_breakpoint_observer.cpp b/src/yuzu/debugger/graphics/graphics_breakpoint_observer.cpp
new file mode 100644
index 000000000..d6d61a739
--- /dev/null
+++ b/src/yuzu/debugger/graphics/graphics_breakpoint_observer.cpp
@@ -0,0 +1,27 @@
+// Copyright 2014 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <QMetaType>
+#include "yuzu/debugger/graphics/graphics_breakpoint_observer.h"
+
+BreakPointObserverDock::BreakPointObserverDock(std::shared_ptr<Tegra::DebugContext> debug_context,
+ const QString& title, QWidget* parent)
+ : QDockWidget(title, parent), BreakPointObserver(debug_context) {
+ qRegisterMetaType<Tegra::DebugContext::Event>("Tegra::DebugContext::Event");
+
+ connect(this, SIGNAL(Resumed()), this, SLOT(OnResumed()));
+
+ // NOTE: This signal is emitted from a non-GUI thread, but connect() takes
+ // care of delaying its handling to the GUI thread.
+ connect(this, SIGNAL(BreakPointHit(Tegra::DebugContext::Event, void*)), this,
+ SLOT(OnBreakPointHit(Tegra::DebugContext::Event, void*)), Qt::BlockingQueuedConnection);
+}
+
+void BreakPointObserverDock::OnMaxwellBreakPointHit(Tegra::DebugContext::Event event, void* data) {
+ emit BreakPointHit(event, data);
+}
+
+void BreakPointObserverDock::OnMaxwellResume() {
+ emit Resumed();
+}
diff --git a/src/yuzu/debugger/graphics/graphics_breakpoint_observer.h b/src/yuzu/debugger/graphics/graphics_breakpoint_observer.h
new file mode 100644
index 000000000..9d05493cf
--- /dev/null
+++ b/src/yuzu/debugger/graphics/graphics_breakpoint_observer.h
@@ -0,0 +1,33 @@
+// Copyright 2014 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <QDockWidget>
+#include "video_core/debug_utils/debug_utils.h"
+
+/**
+ * Utility class which forwards calls to OnMaxwellBreakPointHit and OnMaxwellResume to public slots.
+ * This is because the Maxwell breakpoint callbacks are called from a non-GUI thread, while
+ * the widget usually wants to perform reactions in the GUI thread.
+ */
+class BreakPointObserverDock : public QDockWidget,
+ protected Tegra::DebugContext::BreakPointObserver {
+ Q_OBJECT
+
+public:
+ BreakPointObserverDock(std::shared_ptr<Tegra::DebugContext> debug_context, const QString& title,
+ QWidget* parent = nullptr);
+
+ void OnMaxwellBreakPointHit(Tegra::DebugContext::Event event, void* data) override;
+ void OnMaxwellResume() override;
+
+private slots:
+ virtual void OnBreakPointHit(Tegra::DebugContext::Event event, void* data) = 0;
+ virtual void OnResumed() = 0;
+
+signals:
+ void Resumed();
+ void BreakPointHit(Tegra::DebugContext::Event event, void* data);
+};
diff --git a/src/yuzu/debugger/graphics/graphics_breakpoints.cpp b/src/yuzu/debugger/graphics/graphics_breakpoints.cpp
new file mode 100644
index 000000000..f98cc8152
--- /dev/null
+++ b/src/yuzu/debugger/graphics/graphics_breakpoints.cpp
@@ -0,0 +1,212 @@
+// Copyright 2014 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <QLabel>
+#include <QMetaType>
+#include <QPushButton>
+#include <QTreeView>
+#include <QVBoxLayout>
+#include "common/assert.h"
+#include "yuzu/debugger/graphics/graphics_breakpoints.h"
+#include "yuzu/debugger/graphics/graphics_breakpoints_p.h"
+
+BreakPointModel::BreakPointModel(std::shared_ptr<Tegra::DebugContext> debug_context,
+ QObject* parent)
+ : QAbstractListModel(parent), context_weak(debug_context),
+ at_breakpoint(debug_context->at_breakpoint),
+ active_breakpoint(debug_context->active_breakpoint) {}
+
+int BreakPointModel::columnCount(const QModelIndex& parent) const {
+ return 1;
+}
+
+int BreakPointModel::rowCount(const QModelIndex& parent) const {
+ return static_cast<int>(Tegra::DebugContext::Event::NumEvents);
+}
+
+QVariant BreakPointModel::data(const QModelIndex& index, int role) const {
+ const auto event = static_cast<Tegra::DebugContext::Event>(index.row());
+
+ switch (role) {
+ case Qt::DisplayRole: {
+ if (index.column() == 0) {
+ static const std::map<Tegra::DebugContext::Event, QString> map = {
+ {Tegra::DebugContext::Event::MaxwellCommandLoaded, tr("Maxwell command loaded")},
+ {Tegra::DebugContext::Event::MaxwellCommandProcessed,
+ tr("Maxwell command processed")},
+ {Tegra::DebugContext::Event::IncomingPrimitiveBatch,
+ tr("Incoming primitive batch")},
+ {Tegra::DebugContext::Event::FinishedPrimitiveBatch,
+ tr("Finished primitive batch")},
+ };
+
+ DEBUG_ASSERT(map.size() == static_cast<size_t>(Tegra::DebugContext::Event::NumEvents));
+ return (map.find(event) != map.end()) ? map.at(event) : QString();
+ }
+
+ break;
+ }
+
+ case Qt::CheckStateRole: {
+ if (index.column() == 0)
+ return data(index, Role_IsEnabled).toBool() ? Qt::Checked : Qt::Unchecked;
+ break;
+ }
+
+ case Qt::BackgroundRole: {
+ if (at_breakpoint && index.row() == static_cast<int>(active_breakpoint)) {
+ return QBrush(QColor(0xE0, 0xE0, 0x10));
+ }
+ break;
+ }
+
+ case Role_IsEnabled: {
+ auto context = context_weak.lock();
+ return context && context->breakpoints[(int)event].enabled;
+ }
+
+ default:
+ break;
+ }
+ return QVariant();
+}
+
+Qt::ItemFlags BreakPointModel::flags(const QModelIndex& index) const {
+ if (!index.isValid())
+ return 0;
+
+ Qt::ItemFlags flags = Qt::ItemIsEnabled;
+ if (index.column() == 0)
+ flags |= Qt::ItemIsUserCheckable;
+ return flags;
+}
+
+bool BreakPointModel::setData(const QModelIndex& index, const QVariant& value, int role) {
+ const auto event = static_cast<Tegra::DebugContext::Event>(index.row());
+
+ switch (role) {
+ case Qt::CheckStateRole: {
+ if (index.column() != 0)
+ return false;
+
+ auto context = context_weak.lock();
+ if (!context)
+ return false;
+
+ context->breakpoints[(int)event].enabled = value == Qt::Checked;
+ QModelIndex changed_index = createIndex(index.row(), 0);
+ emit dataChanged(changed_index, changed_index);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void BreakPointModel::OnBreakPointHit(Tegra::DebugContext::Event event) {
+ auto context = context_weak.lock();
+ if (!context)
+ return;
+
+ active_breakpoint = context->active_breakpoint;
+ at_breakpoint = context->at_breakpoint;
+ emit dataChanged(createIndex(static_cast<int>(event), 0),
+ createIndex(static_cast<int>(event), 0));
+}
+
+void BreakPointModel::OnResumed() {
+ auto context = context_weak.lock();
+ if (!context)
+ return;
+
+ at_breakpoint = context->at_breakpoint;
+ emit dataChanged(createIndex(static_cast<int>(active_breakpoint), 0),
+ createIndex(static_cast<int>(active_breakpoint), 0));
+ active_breakpoint = context->active_breakpoint;
+}
+
+GraphicsBreakPointsWidget::GraphicsBreakPointsWidget(
+ std::shared_ptr<Tegra::DebugContext> debug_context, QWidget* parent)
+ : QDockWidget(tr("Maxwell Breakpoints"), parent), Tegra::DebugContext::BreakPointObserver(
+ debug_context) {
+ setObjectName("TegraBreakPointsWidget");
+
+ status_text = new QLabel(tr("Emulation running"));
+ resume_button = new QPushButton(tr("Resume"));
+ resume_button->setEnabled(false);
+
+ breakpoint_model = new BreakPointModel(debug_context, this);
+ breakpoint_list = new QTreeView;
+ breakpoint_list->setRootIsDecorated(false);
+ breakpoint_list->setHeaderHidden(true);
+ breakpoint_list->setModel(breakpoint_model);
+
+ qRegisterMetaType<Tegra::DebugContext::Event>("Tegra::DebugContext::Event");
+
+ connect(breakpoint_list, SIGNAL(doubleClicked(const QModelIndex&)), this,
+ SLOT(OnItemDoubleClicked(const QModelIndex&)));
+
+ connect(resume_button, SIGNAL(clicked()), this, SLOT(OnResumeRequested()));
+
+ connect(this, SIGNAL(BreakPointHit(Tegra::DebugContext::Event, void*)), this,
+ SLOT(OnBreakPointHit(Tegra::DebugContext::Event, void*)), Qt::BlockingQueuedConnection);
+ connect(this, SIGNAL(Resumed()), this, SLOT(OnResumed()));
+
+ connect(this, SIGNAL(BreakPointHit(Tegra::DebugContext::Event, void*)), breakpoint_model,
+ SLOT(OnBreakPointHit(Tegra::DebugContext::Event)), Qt::BlockingQueuedConnection);
+ connect(this, SIGNAL(Resumed()), breakpoint_model, SLOT(OnResumed()));
+
+ connect(this, SIGNAL(BreakPointsChanged(const QModelIndex&, const QModelIndex&)),
+ breakpoint_model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)));
+
+ QWidget* main_widget = new QWidget;
+ auto main_layout = new QVBoxLayout;
+ {
+ auto sub_layout = new QHBoxLayout;
+ sub_layout->addWidget(status_text);
+ sub_layout->addWidget(resume_button);
+ main_layout->addLayout(sub_layout);
+ }
+ main_layout->addWidget(breakpoint_list);
+ main_widget->setLayout(main_layout);
+
+ setWidget(main_widget);
+}
+
+void GraphicsBreakPointsWidget::OnMaxwellBreakPointHit(Event event, void* data) {
+ // Process in GUI thread
+ emit BreakPointHit(event, data);
+}
+
+void GraphicsBreakPointsWidget::OnBreakPointHit(Tegra::DebugContext::Event event, void* data) {
+ status_text->setText(tr("Emulation halted at breakpoint"));
+ resume_button->setEnabled(true);
+}
+
+void GraphicsBreakPointsWidget::OnMaxwellResume() {
+ // Process in GUI thread
+ emit Resumed();
+}
+
+void GraphicsBreakPointsWidget::OnResumed() {
+ status_text->setText(tr("Emulation running"));
+ resume_button->setEnabled(false);
+}
+
+void GraphicsBreakPointsWidget::OnResumeRequested() {
+ if (auto context = context_weak.lock())
+ context->Resume();
+}
+
+void GraphicsBreakPointsWidget::OnItemDoubleClicked(const QModelIndex& index) {
+ if (!index.isValid())
+ return;
+
+ QModelIndex check_index = breakpoint_list->model()->index(index.row(), 0);
+ QVariant enabled = breakpoint_list->model()->data(check_index, Qt::CheckStateRole);
+ QVariant new_state = Qt::Unchecked;
+ if (enabled == Qt::Unchecked)
+ new_state = Qt::Checked;
+ breakpoint_list->model()->setData(check_index, new_state, Qt::CheckStateRole);
+}
diff --git a/src/yuzu/debugger/graphics/graphics_breakpoints.h b/src/yuzu/debugger/graphics/graphics_breakpoints.h
new file mode 100644
index 000000000..ae0ede2e8
--- /dev/null
+++ b/src/yuzu/debugger/graphics/graphics_breakpoints.h
@@ -0,0 +1,46 @@
+// Copyright 2014 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <QDockWidget>
+#include "video_core/debug_utils/debug_utils.h"
+
+class QLabel;
+class QPushButton;
+class QTreeView;
+
+class BreakPointModel;
+
+class GraphicsBreakPointsWidget : public QDockWidget, Tegra::DebugContext::BreakPointObserver {
+ Q_OBJECT
+
+ using Event = Tegra::DebugContext::Event;
+
+public:
+ explicit GraphicsBreakPointsWidget(std::shared_ptr<Tegra::DebugContext> debug_context,
+ QWidget* parent = nullptr);
+
+ void OnMaxwellBreakPointHit(Tegra::DebugContext::Event event, void* data) override;
+ void OnMaxwellResume() override;
+
+public slots:
+ void OnBreakPointHit(Tegra::DebugContext::Event event, void* data);
+ void OnItemDoubleClicked(const QModelIndex&);
+ void OnResumeRequested();
+ void OnResumed();
+
+signals:
+ void Resumed();
+ void BreakPointHit(Tegra::DebugContext::Event event, void* data);
+ void BreakPointsChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight);
+
+private:
+ QLabel* status_text;
+ QPushButton* resume_button;
+
+ BreakPointModel* breakpoint_model;
+ QTreeView* breakpoint_list;
+};
diff --git a/src/yuzu/debugger/graphics/graphics_breakpoints_p.h b/src/yuzu/debugger/graphics/graphics_breakpoints_p.h
new file mode 100644
index 000000000..35a6876ae
--- /dev/null
+++ b/src/yuzu/debugger/graphics/graphics_breakpoints_p.h
@@ -0,0 +1,36 @@
+// Copyright 2014 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <QAbstractListModel>
+#include "video_core/debug_utils/debug_utils.h"
+
+class BreakPointModel : public QAbstractListModel {
+ Q_OBJECT
+
+public:
+ enum {
+ Role_IsEnabled = Qt::UserRole,
+ };
+
+ BreakPointModel(std::shared_ptr<Tegra::DebugContext> context, QObject* parent);
+
+ int columnCount(const QModelIndex& parent = QModelIndex()) const override;
+ int rowCount(const QModelIndex& parent = QModelIndex()) const override;
+ QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
+ Qt::ItemFlags flags(const QModelIndex& index) const override;
+
+ bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override;
+
+public slots:
+ void OnBreakPointHit(Tegra::DebugContext::Event event);
+ void OnResumed();
+
+private:
+ std::weak_ptr<Tegra::DebugContext> context_weak;
+ bool at_breakpoint;
+ Tegra::DebugContext::Event active_breakpoint;
+};
diff --git a/src/yuzu/debugger/graphics/graphics_surface.cpp b/src/yuzu/debugger/graphics/graphics_surface.cpp
new file mode 100644
index 000000000..1fbca8ad0
--- /dev/null
+++ b/src/yuzu/debugger/graphics/graphics_surface.cpp
@@ -0,0 +1,453 @@
+// 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 "core/core.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 "video_core/utils.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");
+ }
+}
+
+SurfacePicture::SurfacePicture(QWidget* parent, GraphicsSurfaceWidget* surface_widget_)
+ : QLabel(parent), surface_widget(surface_widget_) {}
+SurfacePicture::~SurfacePicture() {}
+
+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, SIGNAL(Update()), this, SLOT(OnUpdate()));
+ connect(surface_source_list, SIGNAL(currentIndexChanged(int)), this,
+ SLOT(OnSurfaceSourceChanged(int)));
+ connect(surface_address_control, SIGNAL(ValueChanged(qint64)), this,
+ SLOT(OnSurfaceAddressChanged(qint64)));
+ connect(surface_width_control, SIGNAL(valueChanged(int)), this,
+ SLOT(OnSurfaceWidthChanged(int)));
+ connect(surface_height_control, SIGNAL(valueChanged(int)), this,
+ SLOT(OnSurfaceHeightChanged(int)));
+ connect(surface_format_control, SIGNAL(currentIndexChanged(int)), this,
+ SLOT(OnSurfaceFormatChanged(int)));
+ connect(surface_picker_x_control, SIGNAL(valueChanged(int)), this,
+ SLOT(OnSurfacePickerXChanged(int)));
+ connect(surface_picker_y_control, SIGNAL(valueChanged(int)), this,
+ SLOT(OnSurfacePickerYChanged(int)));
+ connect(save_surface, SIGNAL(clicked()), this, SLOT(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...
+
+ auto& registers = gpu.Get3DEngine().regs;
+ auto& rt = registers.rt[static_cast<size_t>(surface_source) -
+ static_cast<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);
+ boost::optional<VAddr> address = gpu.memory_manager->GpuToCpuAddress(surface_address);
+
+ auto unswizzled_data =
+ Tegra::Texture::UnswizzleTexture(*address, surface_format, surface_width, surface_height);
+
+ 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) {
+ Math::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();
+ boost::optional<VAddr> address = gpu.memory_manager->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
new file mode 100644
index 000000000..6a344bdfc
--- /dev/null
+++ b/src/yuzu/debugger/graphics/graphics_surface.h
@@ -0,0 +1,97 @@
+// 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();
+
+protected slots:
+ virtual void mouseMoveEvent(QMouseEvent* event);
+ virtual void mousePressEvent(QMouseEvent* event);
+
+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();
+
+private slots:
+ void OnBreakPointHit(Tegra::DebugContext::Event event, void* data) override;
+ void OnResumed() override;
+
+ void SaveSurface();
+
+signals:
+ void Update();
+
+private:
+ 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/registers.cpp b/src/yuzu/debugger/registers.cpp
deleted file mode 100644
index 06e2d1647..000000000
--- a/src/yuzu/debugger/registers.cpp
+++ /dev/null
@@ -1,190 +0,0 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <QTreeWidgetItem>
-#include "core/arm/arm_interface.h"
-#include "core/core.h"
-#include "yuzu/debugger/registers.h"
-#include "yuzu/util/util.h"
-
-RegistersWidget::RegistersWidget(QWidget* parent) : QDockWidget(parent) {
- cpu_regs_ui.setupUi(this);
-
- tree = cpu_regs_ui.treeWidget;
- tree->addTopLevelItem(core_registers = new QTreeWidgetItem(QStringList(tr("Registers"))));
- tree->addTopLevelItem(vfp_registers = new QTreeWidgetItem(QStringList(tr("VFP Registers"))));
- tree->addTopLevelItem(vfp_system_registers =
- new QTreeWidgetItem(QStringList(tr("VFP System Registers"))));
- tree->addTopLevelItem(cpsr = new QTreeWidgetItem(QStringList("CPSR")));
-
- for (int i = 0; i < 16; ++i) {
- QTreeWidgetItem* child = new QTreeWidgetItem(QStringList(QString("R[%1]").arg(i)));
- core_registers->addChild(child);
- }
-
- for (int i = 0; i < 32; ++i) {
- QTreeWidgetItem* child = new QTreeWidgetItem(QStringList(QString("S[%1]").arg(i)));
- vfp_registers->addChild(child);
- }
-
- QFont font = GetMonospaceFont();
-
- CreateCPSRChildren();
- CreateVFPSystemRegisterChildren();
-
- // Set Registers to display in monospace font
- for (int i = 0; i < core_registers->childCount(); ++i)
- core_registers->child(i)->setFont(1, font);
-
- for (int i = 0; i < vfp_registers->childCount(); ++i)
- vfp_registers->child(i)->setFont(1, font);
-
- for (int i = 0; i < vfp_system_registers->childCount(); ++i) {
- vfp_system_registers->child(i)->setFont(1, font);
- for (int x = 0; x < vfp_system_registers->child(i)->childCount(); ++x) {
- vfp_system_registers->child(i)->child(x)->setFont(1, font);
- }
- }
- // Set CSPR to display in monospace font
- cpsr->setFont(1, font);
- for (int i = 0; i < cpsr->childCount(); ++i) {
- cpsr->child(i)->setFont(1, font);
- for (int x = 0; x < cpsr->child(i)->childCount(); ++x) {
- cpsr->child(i)->child(x)->setFont(1, font);
- }
- }
- setEnabled(false);
-}
-
-void RegistersWidget::OnDebugModeEntered() {
- if (!Core::System::GetInstance().IsPoweredOn())
- return;
-
- for (int i = 0; i < core_registers->childCount(); ++i)
- core_registers->child(i)->setText(
- 1, QString("0x%1").arg(Core::CPU().GetReg(i), 8, 16, QLatin1Char('0')));
-
- UpdateCPSRValues();
-}
-
-void RegistersWidget::OnDebugModeLeft() {}
-
-void RegistersWidget::OnEmulationStarting(EmuThread* emu_thread) {
- setEnabled(true);
-}
-
-void RegistersWidget::OnEmulationStopping() {
- // Reset widget text
- for (int i = 0; i < core_registers->childCount(); ++i)
- core_registers->child(i)->setText(1, QString(""));
-
- for (int i = 0; i < vfp_registers->childCount(); ++i)
- vfp_registers->child(i)->setText(1, QString(""));
-
- for (int i = 0; i < cpsr->childCount(); ++i)
- cpsr->child(i)->setText(1, QString(""));
-
- cpsr->setText(1, QString(""));
-
- // FPSCR
- for (int i = 0; i < vfp_system_registers->child(0)->childCount(); ++i)
- vfp_system_registers->child(0)->child(i)->setText(1, QString(""));
-
- // FPEXC
- for (int i = 0; i < vfp_system_registers->child(1)->childCount(); ++i)
- vfp_system_registers->child(1)->child(i)->setText(1, QString(""));
-
- vfp_system_registers->child(0)->setText(1, QString(""));
- vfp_system_registers->child(1)->setText(1, QString(""));
- vfp_system_registers->child(2)->setText(1, QString(""));
- vfp_system_registers->child(3)->setText(1, QString(""));
-
- setEnabled(false);
-}
-
-void RegistersWidget::CreateCPSRChildren() {
- cpsr->addChild(new QTreeWidgetItem(QStringList("M")));
- cpsr->addChild(new QTreeWidgetItem(QStringList("T")));
- cpsr->addChild(new QTreeWidgetItem(QStringList("F")));
- cpsr->addChild(new QTreeWidgetItem(QStringList("I")));
- cpsr->addChild(new QTreeWidgetItem(QStringList("A")));
- cpsr->addChild(new QTreeWidgetItem(QStringList("E")));
- cpsr->addChild(new QTreeWidgetItem(QStringList("IT")));
- cpsr->addChild(new QTreeWidgetItem(QStringList("GE")));
- cpsr->addChild(new QTreeWidgetItem(QStringList("DNM")));
- cpsr->addChild(new QTreeWidgetItem(QStringList("J")));
- cpsr->addChild(new QTreeWidgetItem(QStringList("Q")));
- cpsr->addChild(new QTreeWidgetItem(QStringList("V")));
- cpsr->addChild(new QTreeWidgetItem(QStringList("C")));
- cpsr->addChild(new QTreeWidgetItem(QStringList("Z")));
- cpsr->addChild(new QTreeWidgetItem(QStringList("N")));
-}
-
-void RegistersWidget::UpdateCPSRValues() {
- const u32 cpsr_val = Core::CPU().GetCPSR();
-
- cpsr->setText(1, QString("0x%1").arg(cpsr_val, 8, 16, QLatin1Char('0')));
- cpsr->child(0)->setText(
- 1, QString("b%1").arg(cpsr_val & 0x1F, 5, 2, QLatin1Char('0'))); // M - Mode
- cpsr->child(1)->setText(1, QString::number((cpsr_val >> 5) & 1)); // T - State
- cpsr->child(2)->setText(1, QString::number((cpsr_val >> 6) & 1)); // F - FIQ disable
- cpsr->child(3)->setText(1, QString::number((cpsr_val >> 7) & 1)); // I - IRQ disable
- cpsr->child(4)->setText(1, QString::number((cpsr_val >> 8) & 1)); // A - Imprecise abort
- cpsr->child(5)->setText(1, QString::number((cpsr_val >> 9) & 1)); // E - Data endianness
- cpsr->child(6)->setText(1,
- QString::number((cpsr_val >> 10) & 0x3F)); // IT - If-Then state (DNM)
- cpsr->child(7)->setText(1,
- QString::number((cpsr_val >> 16) & 0xF)); // GE - Greater-than-or-Equal
- cpsr->child(8)->setText(1, QString::number((cpsr_val >> 20) & 0xF)); // DNM - Do not modify
- cpsr->child(9)->setText(1, QString::number((cpsr_val >> 24) & 1)); // J - Jazelle
- cpsr->child(10)->setText(1, QString::number((cpsr_val >> 27) & 1)); // Q - Saturation
- cpsr->child(11)->setText(1, QString::number((cpsr_val >> 28) & 1)); // V - Overflow
- cpsr->child(12)->setText(1, QString::number((cpsr_val >> 29) & 1)); // C - Carry/Borrow/Extend
- cpsr->child(13)->setText(1, QString::number((cpsr_val >> 30) & 1)); // Z - Zero
- cpsr->child(14)->setText(1, QString::number((cpsr_val >> 31) & 1)); // N - Negative/Less than
-}
-
-void RegistersWidget::CreateVFPSystemRegisterChildren() {
- QTreeWidgetItem* const fpscr = new QTreeWidgetItem(QStringList("FPSCR"));
- fpscr->addChild(new QTreeWidgetItem(QStringList("IOC")));
- fpscr->addChild(new QTreeWidgetItem(QStringList("DZC")));
- fpscr->addChild(new QTreeWidgetItem(QStringList("OFC")));
- fpscr->addChild(new QTreeWidgetItem(QStringList("UFC")));
- fpscr->addChild(new QTreeWidgetItem(QStringList("IXC")));
- fpscr->addChild(new QTreeWidgetItem(QStringList("IDC")));
- fpscr->addChild(new QTreeWidgetItem(QStringList("IOE")));
- fpscr->addChild(new QTreeWidgetItem(QStringList("DZE")));
- fpscr->addChild(new QTreeWidgetItem(QStringList("OFE")));
- fpscr->addChild(new QTreeWidgetItem(QStringList("UFE")));
- fpscr->addChild(new QTreeWidgetItem(QStringList("IXE")));
- fpscr->addChild(new QTreeWidgetItem(QStringList("IDE")));
- fpscr->addChild(new QTreeWidgetItem(QStringList(tr("Vector Length"))));
- fpscr->addChild(new QTreeWidgetItem(QStringList(tr("Vector Stride"))));
- fpscr->addChild(new QTreeWidgetItem(QStringList(tr("Rounding Mode"))));
- fpscr->addChild(new QTreeWidgetItem(QStringList("FZ")));
- fpscr->addChild(new QTreeWidgetItem(QStringList("DN")));
- fpscr->addChild(new QTreeWidgetItem(QStringList("V")));
- fpscr->addChild(new QTreeWidgetItem(QStringList("C")));
- fpscr->addChild(new QTreeWidgetItem(QStringList("Z")));
- fpscr->addChild(new QTreeWidgetItem(QStringList("N")));
-
- QTreeWidgetItem* const fpexc = new QTreeWidgetItem(QStringList("FPEXC"));
- fpexc->addChild(new QTreeWidgetItem(QStringList("IOC")));
- fpexc->addChild(new QTreeWidgetItem(QStringList("OFC")));
- fpexc->addChild(new QTreeWidgetItem(QStringList("UFC")));
- fpexc->addChild(new QTreeWidgetItem(QStringList("INV")));
- fpexc->addChild(new QTreeWidgetItem(QStringList(tr("Vector Iteration Count"))));
- fpexc->addChild(new QTreeWidgetItem(QStringList("FP2V")));
- fpexc->addChild(new QTreeWidgetItem(QStringList("EN")));
- fpexc->addChild(new QTreeWidgetItem(QStringList("EX")));
-
- vfp_system_registers->addChild(fpscr);
- vfp_system_registers->addChild(fpexc);
- vfp_system_registers->addChild(new QTreeWidgetItem(QStringList("FPINST")));
- vfp_system_registers->addChild(new QTreeWidgetItem(QStringList("FPINST2")));
-}
-
-void RegistersWidget::UpdateVFPSystemRegisterValues() {
- UNIMPLEMENTED();
-}
diff --git a/src/yuzu/debugger/registers.h b/src/yuzu/debugger/registers.h
deleted file mode 100644
index 55bda5b59..000000000
--- a/src/yuzu/debugger/registers.h
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <QDockWidget>
-#include "ui_registers.h"
-
-class QTreeWidget;
-class QTreeWidgetItem;
-class EmuThread;
-
-class RegistersWidget : public QDockWidget {
- Q_OBJECT
-
-public:
- explicit RegistersWidget(QWidget* parent = nullptr);
-
-public slots:
- void OnDebugModeEntered();
- void OnDebugModeLeft();
-
- void OnEmulationStarting(EmuThread* emu_thread);
- void OnEmulationStopping();
-
-private:
- void CreateCPSRChildren();
- void UpdateCPSRValues();
-
- void CreateVFPSystemRegisterChildren();
- void UpdateVFPSystemRegisterValues();
-
- Ui::ARMRegisters cpu_regs_ui;
-
- QTreeWidget* tree;
-
- QTreeWidgetItem* core_registers;
- QTreeWidgetItem* vfp_registers;
- QTreeWidgetItem* vfp_system_registers;
- QTreeWidgetItem* cpsr;
-};
diff --git a/src/yuzu/debugger/registers.ui b/src/yuzu/debugger/registers.ui
deleted file mode 100644
index c81ae03f9..000000000
--- a/src/yuzu/debugger/registers.ui
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>ARMRegisters</class>
- <widget class="QDockWidget" name="ARMRegisters">
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>400</width>
- <height>300</height>
- </rect>
- </property>
- <property name="windowTitle">
- <string>ARM Registers</string>
- </property>
- <widget class="QWidget" name="dockWidgetContents">
- <layout class="QVBoxLayout" name="verticalLayout">
- <item>
- <widget class="QTreeWidget" name="treeWidget">
- <property name="alternatingRowColors">
- <bool>true</bool>
- </property>
- <column>
- <property name="text">
- <string>Register</string>
- </property>
- </column>
- <column>
- <property name="text">
- <string>Value</string>
- </property>
- </column>
- </widget>
- </item>
- </layout>
- </widget>
- </widget>
- <resources/>
- <connections/>
-</ui>
diff --git a/src/yuzu/debugger/wait_tree.cpp b/src/yuzu/debugger/wait_tree.cpp
index 7a62f57b5..017bef13c 100644
--- a/src/yuzu/debugger/wait_tree.cpp
+++ b/src/yuzu/debugger/wait_tree.cpp
@@ -6,8 +6,8 @@
#include "yuzu/util/util.h"
#include "core/core.h"
-#include "core/hle/kernel/condition_variable.h"
#include "core/hle/kernel/event.h"
+#include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/mutex.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/kernel/timer.h"
@@ -51,13 +51,21 @@ std::size_t WaitTreeItem::Row() const {
}
std::vector<std::unique_ptr<WaitTreeThread>> WaitTreeItem::MakeThreadItemList() {
- const auto& threads = Core::System::GetInstance().Scheduler().GetThreadList();
std::vector<std::unique_ptr<WaitTreeThread>> item_list;
- item_list.reserve(threads.size());
- for (std::size_t i = 0; i < threads.size(); ++i) {
- item_list.push_back(std::make_unique<WaitTreeThread>(*threads[i]));
- item_list.back()->row = i;
- }
+ std::size_t row = 0;
+ auto add_threads = [&](const std::vector<Kernel::SharedPtr<Kernel::Thread>>& threads) {
+ for (std::size_t i = 0; i < threads.size(); ++i) {
+ item_list.push_back(std::make_unique<WaitTreeThread>(*threads[i]));
+ item_list.back()->row = row;
+ ++row;
+ }
+ };
+
+ add_threads(Core::System::GetInstance().Scheduler(0)->GetThreadList());
+ add_threads(Core::System::GetInstance().Scheduler(1)->GetThreadList());
+ add_threads(Core::System::GetInstance().Scheduler(2)->GetThreadList());
+ add_threads(Core::System::GetInstance().Scheduler(3)->GetThreadList());
+
return item_list;
}
@@ -67,6 +75,53 @@ QString WaitTreeText::GetText() const {
return text;
}
+WaitTreeMutexInfo::WaitTreeMutexInfo(VAddr mutex_address) : mutex_address(mutex_address) {
+ mutex_value = Memory::Read32(mutex_address);
+ owner_handle = static_cast<Kernel::Handle>(mutex_value & Kernel::Mutex::MutexOwnerMask);
+ owner = Kernel::g_handle_table.Get<Kernel::Thread>(owner_handle);
+}
+
+QString WaitTreeMutexInfo::GetText() const {
+ 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;
+
+ 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)
+ list.push_back(std::make_unique<WaitTreeThread>(*owner));
+ return list;
+}
+
+WaitTreeCallstack::WaitTreeCallstack(const Kernel::Thread& thread) : thread(thread) {}
+
+QString WaitTreeCallstack::GetText() const {
+ return tr("Call stack");
+}
+
+std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeCallstack::GetChildren() const {
+ std::vector<std::unique_ptr<WaitTreeItem>> list;
+
+ constexpr size_t BaseRegister = 29;
+ u64 base_pointer = thread.context.cpu_registers[BaseRegister];
+
+ while (base_pointer != 0) {
+ 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'))));
+ base_pointer = Memory::Read64(base_pointer);
+ }
+
+ return list;
+}
+
WaitTreeWaitObject::WaitTreeWaitObject(const Kernel::WaitObject& o) : object(o) {}
bool WaitTreeExpandableItem::IsExpandable() const {
@@ -84,11 +139,6 @@ std::unique_ptr<WaitTreeWaitObject> WaitTreeWaitObject::make(const Kernel::WaitO
switch (object.GetHandleType()) {
case Kernel::HandleType::Event:
return std::make_unique<WaitTreeEvent>(static_cast<const Kernel::Event&>(object));
- case Kernel::HandleType::Mutex:
- return std::make_unique<WaitTreeMutex>(static_cast<const Kernel::Mutex&>(object));
- case Kernel::HandleType::ConditionVariable:
- return std::make_unique<WaitTreeConditionVariable>(
- static_cast<const Kernel::ConditionVariable&>(object));
case Kernel::HandleType::Timer:
return std::make_unique<WaitTreeTimer>(static_cast<const Kernel::Timer&>(object));
case Kernel::HandleType::Thread:
@@ -150,8 +200,8 @@ QString WaitTreeThread::GetText() const {
case THREADSTATUS_READY:
status = tr("ready");
break;
- case THREADSTATUS_WAIT_ARB:
- status = tr("waiting for address 0x%1").arg(thread.wait_address, 8, 16, QLatin1Char('0'));
+ case THREADSTATUS_WAIT_HLE_EVENT:
+ status = tr("waiting for HLE return");
break;
case THREADSTATUS_WAIT_SLEEP:
status = tr("sleeping");
@@ -160,6 +210,9 @@ QString WaitTreeThread::GetText() const {
case THREADSTATUS_WAIT_SYNCH_ANY:
status = tr("waiting for objects");
break;
+ case THREADSTATUS_WAIT_MUTEX:
+ status = tr("waiting for mutex");
+ break;
case THREADSTATUS_DORMANT:
status = tr("dormant");
break;
@@ -180,12 +233,13 @@ QColor WaitTreeThread::GetColor() const {
return QColor(Qt::GlobalColor::darkGreen);
case THREADSTATUS_READY:
return QColor(Qt::GlobalColor::darkBlue);
- case THREADSTATUS_WAIT_ARB:
+ case THREADSTATUS_WAIT_HLE_EVENT:
return QColor(Qt::GlobalColor::darkRed);
case THREADSTATUS_WAIT_SLEEP:
return QColor(Qt::GlobalColor::darkYellow);
case THREADSTATUS_WAIT_SYNCH_ALL:
case THREADSTATUS_WAIT_SYNCH_ANY:
+ case THREADSTATUS_WAIT_MUTEX:
return QColor(Qt::GlobalColor::red);
case THREADSTATUS_DORMANT:
return QColor(Qt::GlobalColor::darkCyan);
@@ -218,6 +272,9 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const {
}
list.push_back(std::make_unique<WaitTreeText>(tr("processor = %1").arg(processor)));
+ list.push_back(std::make_unique<WaitTreeText>(tr("ideal core = %1").arg(thread.ideal_core)));
+ list.push_back(
+ std::make_unique<WaitTreeText>(tr("affinity mask = %1").arg(thread.affinity_mask)));
list.push_back(std::make_unique<WaitTreeText>(tr("thread id = %1").arg(thread.GetThreadId())));
list.push_back(std::make_unique<WaitTreeText>(tr("priority = %1(current) / %2(normal)")
.arg(thread.current_priority)
@@ -225,17 +282,19 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const {
list.push_back(std::make_unique<WaitTreeText>(
tr("last running ticks = %1").arg(thread.last_running_ticks)));
- if (thread.held_mutexes.empty()) {
- list.push_back(std::make_unique<WaitTreeText>(tr("not holding mutex")));
- } else {
- list.push_back(std::make_unique<WaitTreeMutexList>(thread.held_mutexes));
- }
+ if (thread.mutex_wait_address != 0)
+ list.push_back(std::make_unique<WaitTreeMutexInfo>(thread.mutex_wait_address));
+ else
+ list.push_back(std::make_unique<WaitTreeText>(tr("not waiting for mutex")));
+
if (thread.status == THREADSTATUS_WAIT_SYNCH_ANY ||
thread.status == THREADSTATUS_WAIT_SYNCH_ALL) {
list.push_back(std::make_unique<WaitTreeObjectList>(thread.wait_objects,
thread.IsSleepingOnWaitAll()));
}
+ list.push_back(std::make_unique<WaitTreeCallstack>(thread));
+
return list;
}
@@ -250,33 +309,6 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeEvent::GetChildren() const {
return list;
}
-WaitTreeMutex::WaitTreeMutex(const Kernel::Mutex& object) : WaitTreeWaitObject(object) {}
-
-std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeMutex::GetChildren() const {
- std::vector<std::unique_ptr<WaitTreeItem>> list(WaitTreeWaitObject::GetChildren());
-
- const auto& mutex = static_cast<const Kernel::Mutex&>(object);
- if (mutex.GetHasWaiters()) {
- list.push_back(std::make_unique<WaitTreeText>(tr("locked by thread:")));
- list.push_back(std::make_unique<WaitTreeThread>(*mutex.GetHoldingThread()));
- } else {
- list.push_back(std::make_unique<WaitTreeText>(tr("free")));
- }
- return list;
-}
-
-WaitTreeConditionVariable::WaitTreeConditionVariable(const Kernel::ConditionVariable& object)
- : WaitTreeWaitObject(object) {}
-
-std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeConditionVariable::GetChildren() const {
- std::vector<std::unique_ptr<WaitTreeItem>> list(WaitTreeWaitObject::GetChildren());
-
- const auto& condition_variable = static_cast<const Kernel::ConditionVariable&>(object);
- list.push_back(std::make_unique<WaitTreeText>(
- tr("available count = %1").arg(condition_variable.GetAvailableCount())));
- return list;
-}
-
WaitTreeTimer::WaitTreeTimer(const Kernel::Timer& object) : WaitTreeWaitObject(object) {}
std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeTimer::GetChildren() const {
@@ -293,21 +325,6 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeTimer::GetChildren() const {
return list;
}
-WaitTreeMutexList::WaitTreeMutexList(
- const boost::container::flat_set<Kernel::SharedPtr<Kernel::Mutex>>& list)
- : mutex_list(list) {}
-
-QString WaitTreeMutexList::GetText() const {
- return tr("holding mutexes");
-}
-
-std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeMutexList::GetChildren() const {
- std::vector<std::unique_ptr<WaitTreeItem>> list(mutex_list.size());
- std::transform(mutex_list.begin(), mutex_list.end(), list.begin(),
- [](const auto& t) { return std::make_unique<WaitTreeMutex>(*t); });
- return list;
-}
-
WaitTreeThreadList::WaitTreeThreadList(const std::vector<Kernel::SharedPtr<Kernel::Thread>>& list)
: thread_list(list) {}
diff --git a/src/yuzu/debugger/wait_tree.h b/src/yuzu/debugger/wait_tree.h
index e538174eb..10fc9e968 100644
--- a/src/yuzu/debugger/wait_tree.h
+++ b/src/yuzu/debugger/wait_tree.h
@@ -16,8 +16,6 @@ class EmuThread;
namespace Kernel {
class WaitObject;
class Event;
-class Mutex;
-class ConditionVariable;
class Thread;
class Timer;
} // namespace Kernel
@@ -61,6 +59,31 @@ public:
bool IsExpandable() const override;
};
+class WaitTreeMutexInfo : public WaitTreeExpandableItem {
+ Q_OBJECT
+public:
+ explicit WaitTreeMutexInfo(VAddr mutex_address);
+ QString GetText() const override;
+ std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
+
+private:
+ VAddr mutex_address;
+ u32 mutex_value;
+ Kernel::Handle owner_handle;
+ Kernel::SharedPtr<Kernel::Thread> owner;
+};
+
+class WaitTreeCallstack : public WaitTreeExpandableItem {
+ Q_OBJECT
+public:
+ explicit WaitTreeCallstack(const Kernel::Thread& thread);
+ QString GetText() const override;
+ std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
+
+private:
+ const Kernel::Thread& thread;
+};
+
class WaitTreeWaitObject : public WaitTreeExpandableItem {
Q_OBJECT
public:
@@ -104,20 +127,6 @@ public:
std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
};
-class WaitTreeMutex : public WaitTreeWaitObject {
- Q_OBJECT
-public:
- explicit WaitTreeMutex(const Kernel::Mutex& object);
- std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
-};
-
-class WaitTreeConditionVariable : public WaitTreeWaitObject {
- Q_OBJECT
-public:
- explicit WaitTreeConditionVariable(const Kernel::ConditionVariable& object);
- std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
-};
-
class WaitTreeTimer : public WaitTreeWaitObject {
Q_OBJECT
public:
@@ -125,19 +134,6 @@ public:
std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
};
-class WaitTreeMutexList : public WaitTreeExpandableItem {
- Q_OBJECT
-public:
- explicit WaitTreeMutexList(
- const boost::container::flat_set<Kernel::SharedPtr<Kernel::Mutex>>& list);
-
- QString GetText() const override;
- std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
-
-private:
- const boost::container::flat_set<Kernel::SharedPtr<Kernel::Mutex>>& mutex_list;
-};
-
class WaitTreeThreadList : public WaitTreeExpandableItem {
Q_OBJECT
public:
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index 76ced4de4..bbd681eae 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -315,7 +315,8 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) {
if (!FileUtil::Exists(dir_path.toStdString()) ||
!FileUtil::IsDirectory(dir_path.toStdString())) {
- LOG_ERROR(Frontend, "Could not find game list folder at %s", dir_path.toLocal8Bit().data());
+ NGLOG_ERROR(Frontend, "Could not find game list folder at {}",
+ dir_path.toLocal8Bit().data());
search_field->setFilterResult(0, 0);
return;
}
@@ -364,7 +365,7 @@ static bool HasSupportedFileExtension(const std::string& file_name) {
void GameList::RefreshGameDirectory() {
if (!UISettings::values.gamedir.isEmpty() && current_worker != nullptr) {
- LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list.");
+ NGLOG_INFO(Frontend, "Change detected in the games directory. Reloading game list.");
search_field->clear();
PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
}
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 5802b9855..aa9028399 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -18,7 +18,6 @@
#include "common/logging/log.h"
#include "common/logging/text_formatter.h"
#include "common/microprofile.h"
-#include "common/platform.h"
#include "common/scm_rev.h"
#include "common/scope_exit.h"
#include "common/string_util.h"
@@ -26,12 +25,14 @@
#include "core/gdbstub/gdbstub.h"
#include "core/loader/loader.h"
#include "core/settings.h"
+#include "video_core/debug_utils/debug_utils.h"
#include "yuzu/about_dialog.h"
#include "yuzu/bootmanager.h"
#include "yuzu/configuration/config.h"
#include "yuzu/configuration/configure_dialog.h"
+#include "yuzu/debugger/graphics/graphics_breakpoints.h"
+#include "yuzu/debugger/graphics/graphics_surface.h"
#include "yuzu/debugger/profiler.h"
-#include "yuzu/debugger/registers.h"
#include "yuzu/debugger/wait_tree.h"
#include "yuzu/game_list.h"
#include "yuzu/hotkeys.h"
@@ -42,6 +43,15 @@
Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin);
#endif
+#ifdef _WIN32
+extern "C" {
+// tells Nvidia and AMD drivers to use the dedicated GPU by default on laptops with switchable
+// graphics
+__declspec(dllexport) unsigned long NvOptimusEnablement = 0x00000001;
+__declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
+}
+#endif
+
/**
* "Callouts" are one-time instructional messages shown to the user. In the config settings, there
* is a bitfield "callout_flags" options, used to track if a message has already been shown to the
@@ -69,10 +79,16 @@ static void ShowCalloutMessage(const QString& message, CalloutFlag flag) {
void GMainWindow::ShowCallouts() {}
GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) {
+
+ debug_context = Tegra::DebugContext::Construct();
+
setAcceptDrops(true);
ui.setupUi(this);
statusBar()->hide();
+ default_theme_paths = QIcon::themeSearchPaths();
+ UpdateUITheme();
+
InitializeWidgets();
InitializeDebugWidgets();
InitializeRecentFileMenuActions();
@@ -152,14 +168,15 @@ void GMainWindow::InitializeDebugWidgets() {
debug_menu->addAction(microProfileDialog->toggleViewAction());
#endif
- registersWidget = new RegistersWidget(this);
- addDockWidget(Qt::RightDockWidgetArea, registersWidget);
- registersWidget->hide();
- debug_menu->addAction(registersWidget->toggleViewAction());
- connect(this, &GMainWindow::EmulationStarting, registersWidget,
- &RegistersWidget::OnEmulationStarting);
- connect(this, &GMainWindow::EmulationStopping, registersWidget,
- &RegistersWidget::OnEmulationStopping);
+ graphicsBreakpointsWidget = new GraphicsBreakPointsWidget(debug_context, this);
+ addDockWidget(Qt::RightDockWidgetArea, graphicsBreakpointsWidget);
+ 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);
@@ -308,6 +325,24 @@ void GMainWindow::OnDisplayTitleBars(bool show) {
}
}
+bool GMainWindow::SupportsRequiredGLExtensions() {
+ QStringList unsupported_ext;
+
+ if (!GLAD_GL_ARB_program_interface_query)
+ unsupported_ext.append("ARB_program_interface_query");
+ if (!GLAD_GL_ARB_separate_shader_objects)
+ unsupported_ext.append("ARB_separate_shader_objects");
+ if (!GLAD_GL_ARB_shader_storage_buffer_object)
+ unsupported_ext.append("ARB_shader_storage_buffer_object");
+ if (!GLAD_GL_ARB_vertex_attrib_binding)
+ unsupported_ext.append("ARB_vertex_attrib_binding");
+
+ for (const QString& ext : unsupported_ext)
+ NGLOG_CRITICAL(Frontend, "Unsupported GL extension: {}", ext.toStdString());
+
+ return unsupported_ext.empty();
+}
+
bool GMainWindow::LoadROM(const QString& filename) {
// Shutdown previous session if the emu thread is still active...
if (emu_thread != nullptr)
@@ -323,23 +358,34 @@ bool GMainWindow::LoadROM(const QString& filename) {
return false;
}
+ if (!SupportsRequiredGLExtensions()) {
+ 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. See the log for more details."));
+ return false;
+ }
+
Core::System& system{Core::System::GetInstance()};
- const Core::System::ResultStatus result{system.Load(render_window, filename.toStdString())};
+ system.SetGPUDebugContext(debug_context);
- Core::Telemetry().AddField(Telemetry::FieldType::App, "Frontend", "Qt");
+ const Core::System::ResultStatus result{system.Load(render_window, filename.toStdString())};
if (result != Core::System::ResultStatus::Success) {
switch (result) {
case Core::System::ResultStatus::ErrorGetLoader:
- LOG_CRITICAL(Frontend, "Failed to obtain loader for %s!",
- filename.toStdString().c_str());
+ NGLOG_CRITICAL(Frontend, "Failed to obtain loader for {}!", filename.toStdString());
QMessageBox::critical(this, tr("Error while loading ROM!"),
tr("The ROM format is not supported."));
break;
-
+ case Core::System::ResultStatus::ErrorUnsupportedArch:
+ NGLOG_CRITICAL(Frontend, "Unsupported architecture detected!", filename.toStdString());
+ QMessageBox::critical(this, tr("Error while loading ROM!"),
+ tr("The ROM uses currently unusable 32-bit architecture"));
+ break;
case Core::System::ResultStatus::ErrorSystemMode:
- LOG_CRITICAL(Frontend, "Failed to load ROM!");
+ NGLOG_CRITICAL(Frontend, "Failed to load ROM!");
QMessageBox::critical(this, tr("Error while loading ROM!"),
tr("Could not determine the system mode."));
break;
@@ -351,9 +397,9 @@ bool GMainWindow::LoadROM(const QString& filename) {
"yuzu. A real Switch is required.<br/><br/>"
"For more information on dumping and decrypting games, please see the following "
"wiki pages: <ul>"
- "<li><a href='https://citra-emu.org/wiki/dumping-game-cartridges/'>Dumping Game "
+ "<li><a href='https://yuzu-emu.org/wiki/dumping-game-cartridges/'>Dumping Game "
"Cartridges</a></li>"
- "<li><a href='https://citra-emu.org/wiki/dumping-installed-titles/'>Dumping "
+ "<li><a href='https://yuzu-emu.org/wiki/dumping-installed-titles/'>Dumping "
"Installed Titles</a></li>"
"</ul>"));
break;
@@ -384,11 +430,12 @@ bool GMainWindow::LoadROM(const QString& filename) {
}
return false;
}
+ Core::Telemetry().AddField(Telemetry::FieldType::App, "Frontend", "Qt");
return true;
}
void GMainWindow::BootGame(const QString& filename) {
- LOG_INFO(Frontend, "yuzu starting...");
+ NGLOG_INFO(Frontend, "yuzu starting...");
StoreRecentFile(filename); // Put the filename on top of the list
if (!LoadROM(filename))
@@ -403,17 +450,12 @@ void GMainWindow::BootGame(const QString& filename) {
connect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame);
// BlockingQueuedConnection is important here, it makes sure we've finished refreshing our views
// before the CPU continues
- connect(emu_thread.get(), &EmuThread::DebugModeEntered, registersWidget,
- &RegistersWidget::OnDebugModeEntered, Qt::BlockingQueuedConnection);
connect(emu_thread.get(), &EmuThread::DebugModeEntered, waitTreeWidget,
&WaitTreeWidget::OnDebugModeEntered, Qt::BlockingQueuedConnection);
- connect(emu_thread.get(), &EmuThread::DebugModeLeft, registersWidget,
- &RegistersWidget::OnDebugModeLeft, Qt::BlockingQueuedConnection);
connect(emu_thread.get(), &EmuThread::DebugModeLeft, waitTreeWidget,
&WaitTreeWidget::OnDebugModeLeft, Qt::BlockingQueuedConnection);
// Update the GUI
- registersWidget->OnDebugModeEntered();
if (ui.action_Single_Window_Mode->isChecked()) {
game_list->hide();
}
@@ -636,6 +678,7 @@ void GMainWindow::OnConfigure() {
auto result = configureDialog.exec();
if (result == QDialog::Accepted) {
configureDialog.applyConfiguration();
+ UpdateUITheme();
config->Save();
}
}
@@ -674,18 +717,18 @@ void GMainWindow::UpdateStatusBar() {
void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string details) {
QMessageBox::StandardButton answer;
QString status_message;
- const QString common_message =
- tr("The game you are trying to load requires additional files from your 3DS to be dumped "
- "before playing.<br/><br/>For more information on dumping these files, please see the "
- "following wiki page: <a "
- "href='https://citra-emu.org/wiki/"
- "dumping-system-archives-and-the-shared-fonts-from-a-3ds-console/'>Dumping System "
- "Archives and the Shared Fonts from a 3DS Console</a>.<br/><br/>Would you like to quit "
- "back to the game list? Continuing emulation may result in crashes, corrupted save "
- "data, or other bugs.");
+ const QString common_message = tr(
+ "The game you are trying to load requires additional files from your Switch to be dumped "
+ "before playing.<br/><br/>For more information on dumping these files, please see the "
+ "following wiki page: <a "
+ "href='https://yuzu-emu.org/wiki/"
+ "dumping-system-archives-and-the-shared-fonts-from-a-switch-console/'>Dumping System "
+ "Archives and the Shared Fonts from a Switch Console</a>.<br/><br/>Would you like to quit "
+ "back to the game list? Continuing emulation may result in crashes, corrupted save "
+ "data, or other bugs.");
switch (result) {
case Core::System::ResultStatus::ErrorSystemFiles: {
- QString message = "Citra was unable to locate a 3DS system archive";
+ QString message = "yuzu was unable to locate a Switch system archive";
if (!details.empty()) {
message.append(tr(": %1. ").arg(details.c_str()));
} else {
@@ -700,7 +743,7 @@ void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string det
}
case Core::System::ResultStatus::ErrorSharedFont: {
- QString message = tr("Citra was unable to locate the 3DS shared fonts. ");
+ QString message = tr("yuzu was unable to locate the Switch shared fonts. ");
message.append(common_message);
answer = QMessageBox::question(this, tr("Shared Fonts Not Found"), message,
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
@@ -751,9 +794,11 @@ void GMainWindow::closeEvent(QCloseEvent* event) {
return;
}
- UISettings::values.geometry = saveGeometry();
+ if (ui.action_Fullscreen->isChecked()) {
+ UISettings::values.geometry = saveGeometry();
+ UISettings::values.renderwindow_geometry = render_window->saveGeometry();
+ }
UISettings::values.state = saveState();
- UISettings::values.renderwindow_geometry = render_window->saveGeometry();
#if MICROPROFILE_ENABLED
UISettings::values.microprofile_geometry = microProfileDialog->saveGeometry();
UISettings::values.microprofile_visible = microProfileDialog->isVisible();
@@ -816,6 +861,32 @@ void GMainWindow::filterBarSetChecked(bool state) {
emit(OnToggleFilterBar());
}
+void GMainWindow::UpdateUITheme() {
+ QStringList theme_paths(default_theme_paths);
+ if (UISettings::values.theme != UISettings::themes[0].second &&
+ !UISettings::values.theme.isEmpty()) {
+ QString theme_uri(":" + UISettings::values.theme + "/style.qss");
+ QFile f(theme_uri);
+ if (!f.exists()) {
+ NGLOG_ERROR(Frontend, "Unable to set style, stylesheet file not found");
+ } else {
+ f.open(QFile::ReadOnly | QFile::Text);
+ QTextStream ts(&f);
+ qApp->setStyleSheet(ts.readAll());
+ GMainWindow::setStyleSheet(ts.readAll());
+ }
+ 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");
+ }
+ QIcon::setThemeSearchPaths(theme_paths);
+ emit UpdateThemedIcons();
+}
+
#ifdef main
#undef main
#endif
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 4a0d912bb..3e29d5fc4 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -2,8 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#ifndef _CITRA_QT_MAIN_HXX_
-#define _CITRA_QT_MAIN_HXX_
+#pragma once
#include <memory>
#include <QMainWindow>
@@ -15,17 +14,17 @@ class Config;
class EmuThread;
class GameList;
class GImageInfo;
-class GPUCommandStreamWidget;
-class GPUCommandListWidget;
class GraphicsBreakPointsWidget;
-class GraphicsTracingWidget;
-class GraphicsVertexShaderWidget;
+class GraphicsSurfaceWidget;
class GRenderWindow;
class MicroProfileDialog;
class ProfilerWidget;
-class RegistersWidget;
class WaitTreeWidget;
+namespace Tegra {
+class DebugContext;
+}
+
class GMainWindow : public QMainWindow {
Q_OBJECT
@@ -64,6 +63,9 @@ signals:
*/
void EmulationStopping();
+ // Signal that tells widgets to update icons to use the current theme
+ void UpdateThemedIcons();
+
private:
void InitializeWidgets();
void InitializeDebugWidgets();
@@ -76,6 +78,7 @@ private:
void ConnectWidgetEvents();
void ConnectMenuEvents();
+ bool SupportsRequiredGLExtensions();
bool LoadROM(const QString& filename);
void BootGame(const QString& filename);
void ShutdownGame();
@@ -138,6 +141,8 @@ private:
Ui::MainWindow ui;
+ std::shared_ptr<Tegra::DebugContext> debug_context;
+
GRenderWindow* render_window;
GameList* game_list;
@@ -150,22 +155,24 @@ private:
std::unique_ptr<Config> config;
- // Whether emulation is currently running in Citra.
+ // Whether emulation is currently running in yuzu.
bool emulation_running = false;
std::unique_ptr<EmuThread> emu_thread;
// Debugger panes
ProfilerWidget* profilerWidget;
MicroProfileDialog* microProfileDialog;
- RegistersWidget* registersWidget;
+ GraphicsBreakPointsWidget* graphicsBreakpointsWidget;
+ GraphicsSurfaceWidget* graphicsSurfaceWidget;
WaitTreeWidget* waitTreeWidget;
QAction* actions_recent_files[max_recent_files_item];
+ // stores default icon theme search paths for the platform
+ QStringList default_theme_paths;
+
protected:
void dropEvent(QDropEvent* event) override;
void dragEnterEvent(QDragEnterEvent* event) override;
void dragMoveEvent(QDragMoveEvent* event) override;
};
-
-#endif // _CITRA_QT_MAIN_HXX_
diff --git a/src/yuzu/ui_settings.h b/src/yuzu/ui_settings.h
index 9036ce2c1..8e215a002 100644
--- a/src/yuzu/ui_settings.h
+++ b/src/yuzu/ui_settings.h
@@ -15,6 +15,10 @@ namespace UISettings {
using ContextualShortcut = std::pair<QString, int>;
using Shortcut = std::pair<QString, ContextualShortcut>;
+static const std::array<std::pair<QString, QString>, 2> themes = {
+ {std::make_pair(QString("Default"), QString("default")),
+ std::make_pair(QString("Dark"), QString("qdarkstyle"))}};
+
struct Values {
QByteArray geometry;
QByteArray state;
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp
index bf79d2e81..ee6e4d658 100644
--- a/src/yuzu_cmd/config.cpp
+++ b/src/yuzu_cmd/config.cpp
@@ -27,17 +27,17 @@ bool Config::LoadINI(const std::string& default_contents, bool retry) {
const char* location = this->sdl2_config_loc.c_str();
if (sdl2_config->ParseError() < 0) {
if (retry) {
- LOG_WARNING(Config, "Failed to load %s. Creating file from defaults...", location);
+ NGLOG_WARNING(Config, "Failed to load {}. Creating file from defaults...", location);
FileUtil::CreateFullPath(location);
FileUtil::WriteStringToFile(true, default_contents, location);
sdl2_config = std::make_unique<INIReader>(location); // Reopen file
return LoadINI(default_contents, false);
}
- LOG_ERROR(Config, "Failed.");
+ NGLOG_ERROR(Config, "Failed.");
return false;
}
- LOG_INFO(Config, "Successfully loaded %s", location);
+ NGLOG_INFO(Config, "Successfully loaded {}", location);
return true;
}
@@ -90,8 +90,8 @@ void Config::ReadValues() {
sdl2_config->Get("Controls", "touch_device", "engine:emu_window");
// Core
- Settings::values.cpu_core =
- static_cast<Settings::CpuCore>(sdl2_config->GetInteger("Core", "cpu_core", 0));
+ Settings::values.use_cpu_jit = sdl2_config->GetBoolean("Core", "use_cpu_jit", true);
+ Settings::values.use_multi_core = sdl2_config->GetBoolean("Core", "use_multi_core", false);
// Renderer
Settings::values.resolution_factor =
@@ -107,6 +107,9 @@ void Config::ReadValues() {
Settings::values.use_virtual_sd =
sdl2_config->GetBoolean("Data Storage", "use_virtual_sd", true);
+ // System
+ Settings::values.use_docked_mode = sdl2_config->GetBoolean("System", "use_docked_mode", true);
+
// Miscellaneous
Settings::values.log_filter = sdl2_config->Get("Miscellaneous", "log_filter", "*:Trace");
diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h
index 156f7a1a4..1c438c3f5 100644
--- a/src/yuzu_cmd/default_ini.h
+++ b/src/yuzu_cmd/default_ini.h
@@ -8,7 +8,7 @@ namespace DefaultINI {
const char* sdl2_config_file = R"(
[Controls]
-# The input devices and parameters for each 3DS native input
+# The input devices and parameters for each Switch native input
# It should be in the format of "engine:[engine_name],[param1]:[value1],[param2]:[value2]..."
# Escape characters $0 (for ':'), $1 (for ',') and $2 (for '$') can be used in values
@@ -76,9 +76,13 @@ motion_device=
touch_device=
[Core]
-# Which CPU core to use for CPU emulation
-# 0 (default): Unicorn (slow), 1: Dynarmic (faster)
-cpu_core =
+# Whether to use the Just-In-Time (JIT) compiler for CPU emulation
+# 0: Interpreter (slow), 1 (default): JIT (fast)
+use_cpu_jit =
+
+# Whether to use multi-core for CPU emulation
+# 0 (default): Disabled, 1: Enabled
+use_multi_core=
[Renderer]
# Whether to use software or hardware rendering.
@@ -154,9 +158,9 @@ output_device =
use_virtual_sd =
[System]
-# The system model that Citra will try to emulate
-# 0: Old 3DS (default), 1: New 3DS
-is_new_3ds =
+# Whether the system is docked
+# 1 (default): Yes, 0: No
+use_docked_mode =
# The system region that Citra will use during emulation
# -1: Auto-select (default), 0: Japan, 1: USA, 2: Europe, 3: Australia, 4: China, 5: Korea, 6: Taiwan
@@ -177,12 +181,12 @@ gdbstub_port=24689
# 0: No, 1 (default): Yes
enable_telemetry =
# Endpoint URL for submitting telemetry data
-telemetry_endpoint_url = https://services.citra-emu.org/api/telemetry
+telemetry_endpoint_url =
# Endpoint URL to verify the username and token
-verify_endpoint_url = https://services.citra-emu.org/api/profile
-# Username and token for Citra Web Service
+verify_endpoint_url =
+# Username and token for yuzu Web Service
# See https://services.citra-emu.org/ for more info
-citra_username =
-citra_token =
+yuzu_username =
+yuzu_token =
)";
}
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
index 3d7cd06a4..cfd8eb7e6 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
@@ -7,6 +7,7 @@
#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"
@@ -56,14 +57,53 @@ void EmuWindow_SDL2::OnResize() {
UpdateCurrentFramebufferLayout(width, height);
}
-EmuWindow_SDL2::EmuWindow_SDL2() {
+void EmuWindow_SDL2::Fullscreen() {
+ if (SDL_SetWindowFullscreen(render_window, SDL_WINDOW_FULLSCREEN) == 0) {
+ return;
+ }
+
+ NGLOG_ERROR(Frontend, "Fullscreening failed: {}", SDL_GetError());
+
+ // Try a different fullscreening method
+ NGLOG_INFO(Frontend, "Attempting to use borderless fullscreen...");
+ if (SDL_SetWindowFullscreen(render_window, SDL_WINDOW_FULLSCREEN_DESKTOP) == 0) {
+ return;
+ }
+
+ NGLOG_ERROR(Frontend, "Borderless fullscreening failed: {}", SDL_GetError());
+
+ // Fallback algorithm: Maximise window.
+ // Works on all systems (unless something is seriously wrong), so no fallback for this one.
+ NGLOG_INFO(Frontend, "Falling back on a maximised window...");
+ SDL_MaximizeWindow(render_window);
+}
+
+bool EmuWindow_SDL2::SupportsRequiredGLExtensions() {
+ std::vector<std::string> unsupported_ext;
+
+ if (!GLAD_GL_ARB_program_interface_query)
+ unsupported_ext.push_back("ARB_program_interface_query");
+ if (!GLAD_GL_ARB_separate_shader_objects)
+ unsupported_ext.push_back("ARB_separate_shader_objects");
+ if (!GLAD_GL_ARB_shader_storage_buffer_object)
+ unsupported_ext.push_back("ARB_shader_storage_buffer_object");
+ if (!GLAD_GL_ARB_vertex_attrib_binding)
+ unsupported_ext.push_back("ARB_vertex_attrib_binding");
+
+ for (const std::string& ext : unsupported_ext)
+ NGLOG_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) < 0) {
- LOG_CRITICAL(Frontend, "Failed to initialize SDL2! Exiting...");
+ NGLOG_CRITICAL(Frontend, "Failed to initialize SDL2! Exiting...");
exit(1);
}
@@ -76,8 +116,8 @@ EmuWindow_SDL2::EmuWindow_SDL2() {
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0);
- std::string window_title = Common::StringFromFormat("yuzu %s| %s-%s ", Common::g_build_name,
- Common::g_scm_branch, Common::g_scm_desc);
+ std::string window_title = fmt::format("yuzu {} | {}-{}", Common::g_build_name,
+ Common::g_scm_branch, Common::g_scm_desc);
render_window =
SDL_CreateWindow(window_title.c_str(),
SDL_WINDOWPOS_UNDEFINED, // x position
@@ -86,19 +126,28 @@ EmuWindow_SDL2::EmuWindow_SDL2() {
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
if (render_window == nullptr) {
- LOG_CRITICAL(Frontend, "Failed to create SDL2 window! Exiting...");
+ NGLOG_CRITICAL(Frontend, "Failed to create SDL2 window! Exiting...");
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! Exiting...");
+ NGLOG_CRITICAL(Frontend, "Failed to create SDL2 GL context! Exiting...");
exit(1);
}
if (!gladLoadGLLoader(static_cast<GLADloadproc>(SDL_GL_GetProcAddress))) {
- LOG_CRITICAL(Frontend, "Failed to initialize GL functions! Exiting...");
+ NGLOG_CRITICAL(Frontend, "Failed to initialize GL functions! Exiting...");
+ exit(1);
+ }
+
+ if (!SupportsRequiredGLExtensions()) {
+ NGLOG_CRITICAL(Frontend, "GPU does not support all required OpenGL extensions! Exiting...");
exit(1);
}
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.h b/src/yuzu_cmd/emu_window/emu_window_sdl2.h
index 3664d2fbe..1d835c3c6 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2.h
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.h
@@ -12,7 +12,7 @@ struct SDL_Window;
class EmuWindow_SDL2 : public EmuWindow {
public:
- EmuWindow_SDL2();
+ explicit EmuWindow_SDL2(bool fullscreen);
~EmuWindow_SDL2();
/// Swap buffers to display the next frame
@@ -43,6 +43,12 @@ private:
/// Called by PollEvents when any event that may cause the window to be resized occurs
void OnResize();
+ /// 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;
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp
index 8f7c75796..95e568b7b 100644
--- a/src/yuzu_cmd/yuzu.cpp
+++ b/src/yuzu_cmd/yuzu.cpp
@@ -7,8 +7,19 @@
#include <string>
#include <thread>
-// This needs to be included before getopt.h because the latter #defines symbols used by it
+#include "common/logging/backend.h"
+#include "common/logging/filter.h"
+#include "common/logging/log.h"
#include "common/microprofile.h"
+#include "common/scm_rev.h"
+#include "common/scope_exit.h"
+#include "common/string_util.h"
+#include "core/core.h"
+#include "core/gdbstub/gdbstub.h"
+#include "core/loader/loader.h"
+#include "core/settings.h"
+#include "yuzu_cmd/config.h"
+#include "yuzu_cmd/emu_window/emu_window_sdl2.h"
#ifdef _MSC_VER
#include <getopt.h>
@@ -24,23 +35,20 @@
#include <shellapi.h>
#endif
-#include "common/logging/backend.h"
-#include "common/logging/filter.h"
-#include "common/logging/log.h"
-#include "common/scm_rev.h"
-#include "common/scope_exit.h"
-#include "common/string_util.h"
-#include "core/core.h"
-#include "core/gdbstub/gdbstub.h"
-#include "core/loader/loader.h"
-#include "core/settings.h"
-#include "yuzu_cmd/config.h"
-#include "yuzu_cmd/emu_window/emu_window_sdl2.h"
+#ifdef _WIN32
+extern "C" {
+// tells Nvidia and AMD drivers to use the dedicated GPU by default on laptops with switchable
+// graphics
+__declspec(dllexport) unsigned long NvOptimusEnablement = 0x00000001;
+__declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
+}
+#endif
static void PrintHelp(const char* argv0) {
std::cout << "Usage: " << argv0
<< " [options] <filename>\n"
"-g, --gdbport=NUMBER Enable gdb stub on port NUMBER\n"
+ "-f, --fullscreen Start in fullscreen mode\n"
"-h, --help Display this help and exit\n"
"-v, --version Output version information and exit\n";
}
@@ -61,21 +69,24 @@ int main(int argc, char** argv) {
auto argv_w = CommandLineToArgvW(GetCommandLineW(), &argc_w);
if (argv_w == nullptr) {
- LOG_CRITICAL(Frontend, "Failed to get command line arguments");
+ NGLOG_CRITICAL(Frontend, "Failed to get command line arguments");
return -1;
}
#endif
std::string filepath;
+ bool fullscreen = false;
+
static struct option long_options[] = {
{"gdbport", required_argument, 0, 'g'},
+ {"fullscreen", no_argument, 0, 'f'},
{"help", no_argument, 0, 'h'},
{"version", no_argument, 0, 'v'},
{0, 0, 0, 0},
};
while (optind < argc) {
- char arg = getopt_long(argc, argv, "g:hv", long_options, &option_index);
+ char arg = getopt_long(argc, argv, "g:fhv", long_options, &option_index);
if (arg != -1) {
switch (arg) {
case 'g':
@@ -89,6 +100,10 @@ int main(int argc, char** argv) {
exit(1);
}
break;
+ case 'f':
+ fullscreen = true;
+ NGLOG_INFO(Frontend, "Starting in fullscreen mode...");
+ break;
case 'h':
PrintHelp(argv[0]);
return 0;
@@ -117,7 +132,7 @@ int main(int argc, char** argv) {
SCOPE_EXIT({ MicroProfileShutdown(); });
if (filepath.empty()) {
- LOG_CRITICAL(Frontend, "Failed to load ROM: No ROM specified");
+ NGLOG_CRITICAL(Frontend, "Failed to load ROM: No ROM specified");
return -1;
}
@@ -128,7 +143,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>()};
+ std::unique_ptr<EmuWindow_SDL2> emu_window{std::make_unique<EmuWindow_SDL2>(fullscreen)};
Core::System& system{Core::System::GetInstance()};
@@ -138,28 +153,28 @@ 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());
+ NGLOG_CRITICAL(Frontend, "Failed to obtain loader for %s!", filepath.c_str());
return -1;
case Core::System::ResultStatus::ErrorLoader:
- LOG_CRITICAL(Frontend, "Failed to load ROM!");
+ NGLOG_CRITICAL(Frontend, "Failed to load ROM!");
return -1;
case Core::System::ResultStatus::ErrorLoader_ErrorEncrypted:
- LOG_CRITICAL(Frontend, "The game that you are trying to load must be decrypted before "
- "being used with yuzu. \n\n For more information on dumping and "
- "decrypting games, please refer to: "
- "https://citra-emu.org/wiki/dumping-game-cartridges/");
+ NGLOG_CRITICAL(Frontend, "The game that you are trying to load must be decrypted before "
+ "being used with yuzu. \n\n For more information on dumping and "
+ "decrypting games, please refer to: "
+ "https://yuzu-emu.org/wiki/dumping-game-cartridges/");
return -1;
case Core::System::ResultStatus::ErrorLoader_ErrorInvalidFormat:
- LOG_CRITICAL(Frontend, "Error while loading ROM: The ROM format is not supported.");
+ NGLOG_CRITICAL(Frontend, "Error while loading ROM: The ROM format is not supported.");
return -1;
case Core::System::ResultStatus::ErrorNotInitialized:
- LOG_CRITICAL(Frontend, "CPUCore not initialized");
+ NGLOG_CRITICAL(Frontend, "CPUCore not initialized");
return -1;
case Core::System::ResultStatus::ErrorSystemMode:
- LOG_CRITICAL(Frontend, "Failed to determine system mode!");
+ NGLOG_CRITICAL(Frontend, "Failed to determine system mode!");
return -1;
case Core::System::ResultStatus::ErrorVideoCore:
- LOG_CRITICAL(Frontend, "VideoCore not initialized");
+ NGLOG_CRITICAL(Frontend, "VideoCore not initialized");
return -1;
case Core::System::ResultStatus::Success:
break; // Expected case
diff --git a/src/yuzu_cmd/yuzu.rc b/src/yuzu_cmd/yuzu.rc
index 7cb8a14e1..7de8ef3d9 100644
--- a/src/yuzu_cmd/yuzu.rc
+++ b/src/yuzu_cmd/yuzu.rc
@@ -6,7 +6,7 @@
// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
-CITRA_ICON ICON "../../dist/yuzu.ico"
+YUZU_ICON ICON "../../dist/yuzu.ico"
/////////////////////////////////////////////////////////////////////////////