summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/CMakeLists.txt8
-rw-r--r--src/audio_core/CMakeLists.txt3
-rw-r--r--src/audio_core/cubeb_sink.cpp25
-rw-r--r--src/audio_core/sdl2_sink.cpp4
-rw-r--r--src/audio_core/time_stretch.cpp68
-rw-r--r--src/audio_core/time_stretch.h34
-rw-r--r--src/common/atomic_ops.h90
-rw-r--r--src/common/fiber.cpp5
-rw-r--r--src/common/host_memory.cpp4
-rw-r--r--src/common/logging/filter.cpp2
-rw-r--r--src/common/logging/types.h2
-rw-r--r--src/common/math_util.h50
-rw-r--r--src/common/settings.h6
-rw-r--r--src/common/settings_input.h1
-rw-r--r--src/common/thread.h2
-rw-r--r--src/common/threadsafe_queue.h4
-rw-r--r--src/common/x64/native_clock.cpp59
-rw-r--r--src/core/CMakeLists.txt54
-rw-r--r--src/core/arm/arm_interface.cpp228
-rw-r--r--src/core/arm/arm_interface.h21
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_32.cpp152
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_32.h19
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_64.cpp206
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_64.h18
-rw-r--r--src/core/arm/symbols.cpp190
-rw-r--r--src/core/arm/symbols.h27
-rw-r--r--src/core/core_timing.h6
-rw-r--r--src/core/file_sys/patch_manager.cpp63
-rw-r--r--src/core/file_sys/registered_cache.cpp6
-rw-r--r--src/core/frontend/applets/mii.cpp19
-rw-r--r--src/core/frontend/applets/mii.h34
-rw-r--r--src/core/frontend/applets/mii_edit.cpp18
-rw-r--r--src/core/frontend/applets/mii_edit.h23
-rw-r--r--src/core/hid/emulated_console.cpp17
-rw-r--r--src/core/hid/emulated_console.h1
-rw-r--r--src/core/hid/emulated_controller.cpp377
-rw-r--r--src/core/hid/emulated_controller.h3
-rw-r--r--src/core/hid/emulated_devices.cpp78
-rw-r--r--src/core/hid/emulated_devices.h35
-rw-r--r--src/core/hid/hid_types.h5
-rw-r--r--src/core/hle/kernel/board/nintendo/nx/k_system_control.cpp6
-rw-r--r--src/core/hle/kernel/global_scheduler_context.h3
-rw-r--r--src/core/hle/kernel/hle_ipc.cpp16
-rw-r--r--src/core/hle/kernel/hle_ipc.h8
-rw-r--r--src/core/hle/kernel/k_auto_object.h7
-rw-r--r--src/core/hle/kernel/k_code_memory.cpp14
-rw-r--r--src/core/hle/kernel/k_page_linked_list.h4
-rw-r--r--src/core/hle/kernel/k_page_table.cpp253
-rw-r--r--src/core/hle/kernel/k_page_table.h38
-rw-r--r--src/core/hle/kernel/k_process.h2
-rw-r--r--src/core/hle/kernel/k_scheduler.cpp8
-rw-r--r--src/core/hle/kernel/k_scheduler_lock.h3
-rw-r--r--src/core/hle/kernel/k_server_port.cpp6
-rw-r--r--src/core/hle/kernel/k_server_session.cpp3
-rw-r--r--src/core/hle/kernel/k_spin_lock.cpp39
-rw-r--r--src/core/hle/kernel/k_spin_lock.h4
-rw-r--r--src/core/hle/kernel/k_thread.cpp39
-rw-r--r--src/core/hle/kernel/k_thread.h16
-rw-r--r--src/core/hle/kernel/kernel.cpp93
-rw-r--r--src/core/hle/kernel/kernel.h30
-rw-r--r--src/core/hle/kernel/physical_core.cpp4
-rw-r--r--src/core/hle/kernel/physical_core.h7
-rw-r--r--src/core/hle/kernel/svc.cpp16
-rw-r--r--src/core/hle/kernel/time_manager.cpp4
-rw-r--r--src/core/hle/service/am/am.cpp9
-rw-r--r--src/core/hle/service/am/am.h1
-rw-r--r--src/core/hle/service/am/applets/applet_mii.cpp100
-rw-r--r--src/core/hle/service/am/applets/applet_mii.h90
-rw-r--r--src/core/hle/service/am/applets/applet_mii_edit.cpp139
-rw-r--r--src/core/hle/service/am/applets/applet_mii_edit.h45
-rw-r--r--src/core/hle/service/am/applets/applet_mii_edit_types.h83
-rw-r--r--src/core/hle/service/am/applets/applet_software_keyboard.cpp474
-rw-r--r--src/core/hle/service/am/applets/applet_software_keyboard.h35
-rw-r--r--src/core/hle/service/am/applets/applet_software_keyboard_types.h72
-rw-r--r--src/core/hle/service/am/applets/applet_web_browser.cpp8
-rw-r--r--src/core/hle/service/am/applets/applets.cpp28
-rw-r--r--src/core/hle/service/am/applets/applets.h8
-rw-r--r--src/core/hle/service/audio/audout_u.cpp7
-rw-r--r--src/core/hle/service/audio/audren_u.cpp3
-rw-r--r--src/core/hle/service/filesystem/filesystem.cpp2
-rw-r--r--src/core/hle/service/filesystem/fsp_srv.cpp13
-rw-r--r--src/core/hle/service/hid/controllers/npad.cpp307
-rw-r--r--src/core/hle/service/hid/controllers/npad.h149
-rw-r--r--src/core/hle/service/hid/errors.h4
-rw-r--r--src/core/hle/service/hid/hid.cpp160
-rw-r--r--src/core/hle/service/hid/hid.h1
-rw-r--r--src/core/hle/service/hid/hidbus.cpp531
-rw-r--r--src/core/hle/service/hid/hidbus.h131
-rw-r--r--src/core/hle/service/hid/hidbus/hidbus_base.cpp72
-rw-r--r--src/core/hle/service/hid/hidbus/hidbus_base.h179
-rw-r--r--src/core/hle/service/hid/hidbus/ringcon.cpp286
-rw-r--r--src/core/hle/service/hid/hidbus/ringcon.h254
-rw-r--r--src/core/hle/service/hid/hidbus/starlink.cpp51
-rw-r--r--src/core/hle/service/hid/hidbus/starlink.h39
-rw-r--r--src/core/hle/service/hid/hidbus/stubbed.cpp52
-rw-r--r--src/core/hle/service/hid/hidbus/stubbed.h39
-rw-r--r--src/core/hle/service/jit/jit.cpp332
-rw-r--r--src/core/hle/service/jit/jit.h20
-rw-r--r--src/core/hle/service/jit/jit_context.cpp424
-rw-r--r--src/core/hle/service/jit/jit_context.h65
-rw-r--r--src/core/hle/service/ldr/ldr.cpp33
-rw-r--r--src/core/hle/service/mii/mii_manager.cpp3
-rw-r--r--src/core/hle/service/mii/mii_manager.h303
-rw-r--r--src/core/hle/service/mii/raw_data.h2
-rw-r--r--src/core/hle/service/mii/types.h307
-rw-r--r--src/core/hle/service/nfp/nfp.cpp1
-rw-r--r--src/core/hle/service/nfp/nfp.h2
-rw-r--r--src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp10
-rw-r--r--src/core/hle/service/nvdrv/devices/nvdisp_disp0.h7
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp2
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp7
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_gpu.h10
-rw-r--r--src/core/hle/service/nvdrv/nvdata.h10
-rw-r--r--src/core/hle/service/nvdrv/nvdrv.h3
-rw-r--r--src/core/hle/service/nvdrv/nvdrv_interface.cpp14
-rw-r--r--src/core/hle/service/nvflinger/binder.h42
-rw-r--r--src/core/hle/service/nvflinger/buffer_item.h46
-rw-r--r--src/core/hle/service/nvflinger/buffer_item_consumer.cpp59
-rw-r--r--src/core/hle/service/nvflinger/buffer_item_consumer.h28
-rw-r--r--src/core/hle/service/nvflinger/buffer_queue.cpp206
-rw-r--r--src/core/hle/service/nvflinger/buffer_queue.h154
-rw-r--r--src/core/hle/service/nvflinger/buffer_queue_consumer.cpp231
-rw-r--r--src/core/hle/service/nvflinger/buffer_queue_consumer.h37
-rw-r--r--src/core/hle/service/nvflinger/buffer_queue_core.cpp117
-rw-r--r--src/core/hle/service/nvflinger/buffer_queue_core.h79
-rw-r--r--src/core/hle/service/nvflinger/buffer_queue_defs.h21
-rw-r--r--src/core/hle/service/nvflinger/buffer_queue_producer.cpp923
-rw-r--r--src/core/hle/service/nvflinger/buffer_queue_producer.h83
-rw-r--r--src/core/hle/service/nvflinger/buffer_slot.h39
-rw-r--r--src/core/hle/service/nvflinger/buffer_transform_flags.h25
-rw-r--r--src/core/hle/service/nvflinger/consumer_base.cpp133
-rw-r--r--src/core/hle/service/nvflinger/consumer_base.h60
-rw-r--r--src/core/hle/service/nvflinger/consumer_listener.h26
-rw-r--r--src/core/hle/service/nvflinger/graphic_buffer_producer.cpp18
-rw-r--r--src/core/hle/service/nvflinger/graphic_buffer_producer.h76
-rw-r--r--src/core/hle/service/nvflinger/hos_binder_driver_server.cpp36
-rw-r--r--src/core/hle/service/nvflinger/hos_binder_driver_server.h37
-rw-r--r--src/core/hle/service/nvflinger/nvflinger.cpp85
-rw-r--r--src/core/hle/service/nvflinger/nvflinger.h26
-rw-r--r--src/core/hle/service/nvflinger/parcel.h172
-rw-r--r--src/core/hle/service/nvflinger/pixel_format.h21
-rw-r--r--src/core/hle/service/nvflinger/producer_listener.h16
-rw-r--r--src/core/hle/service/nvflinger/status.h28
-rw-r--r--src/core/hle/service/nvflinger/ui/fence.h32
-rw-r--r--src/core/hle/service/nvflinger/ui/graphic_buffer.h100
-rw-r--r--src/core/hle/service/nvflinger/window.h53
-rw-r--r--src/core/hle/service/service.cpp13
-rw-r--r--src/core/hle/service/service.h23
-rw-r--r--src/core/hle/service/sm/sm.cpp10
-rw-r--r--src/core/hle/service/sm/sm.h2
-rw-r--r--src/core/hle/service/sockets/bsd.cpp6
-rw-r--r--src/core/hle/service/sockets/sfdnsres.cpp205
-rw-r--r--src/core/hle/service/sockets/sfdnsres.h1
-rw-r--r--src/core/hle/service/sockets/sockets.h1
-rw-r--r--src/core/hle/service/vi/display/vi_display.cpp50
-rw-r--r--src/core/hle/service/vi/display/vi_display.h29
-rw-r--r--src/core/hle/service/vi/layer/vi_layer.cpp6
-rw-r--r--src/core/hle/service/vi/layer/vi_layer.h57
-rw-r--r--src/core/hle/service/vi/vi.cpp691
-rw-r--r--src/core/hle/service/vi/vi.h10
-rw-r--r--src/core/hle/service/vi/vi_m.cpp9
-rw-r--r--src/core/hle/service/vi/vi_m.h7
-rw-r--r--src/core/hle/service/vi/vi_s.cpp9
-rw-r--r--src/core/hle/service/vi/vi_s.h7
-rw-r--r--src/core/hle/service/vi/vi_u.cpp9
-rw-r--r--src/core/hle/service/vi/vi_u.h7
-rw-r--r--src/core/network/network.cpp4
-rw-r--r--src/core/network/sockets.h2
-rw-r--r--src/core/perf_stats.cpp10
-rw-r--r--src/core/tools/freezer.cpp18
-rw-r--r--src/input_common/drivers/sdl_driver.cpp12
-rw-r--r--src/input_common/helpers/udp_protocol.h9
-rw-r--r--src/input_common/input_engine.cpp46
-rw-r--r--src/shader_recompiler/backend/glsl/emit_glsl_context_get_set.cpp25
-rw-r--r--src/shader_recompiler/backend/glsl/glsl_emit_context.cpp19
-rw-r--r--src/shader_recompiler/backend/glsl/glsl_emit_context.h1
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp2
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp1
-rw-r--r--src/shader_recompiler/backend/spirv/spirv_emit_context.cpp22
-rw-r--r--src/shader_recompiler/frontend/ir/microinstruction.cpp5
-rw-r--r--src/shader_recompiler/frontend/ir/value.h4
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/exit_program.cpp4
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/texture_load.cpp2
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/texture_mipmap_level.cpp2
-rw-r--r--src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp9
-rw-r--r--src/shader_recompiler/ir_opt/dead_code_elimination_pass.cpp98
-rw-r--r--src/shader_recompiler/program_header.h5
-rw-r--r--src/shader_recompiler/shader_info.h1
-rw-r--r--src/video_core/buffer_cache/buffer_base.h3
-rw-r--r--src/video_core/buffer_cache/buffer_cache.h49
-rw-r--r--src/video_core/command_classes/codecs/codec.cpp35
-rw-r--r--src/video_core/engines/maxwell_3d.cpp5
-rw-r--r--src/video_core/engines/maxwell_3d.h7
-rw-r--r--src/video_core/framebuffer_config.h29
-rw-r--r--src/video_core/gpu.cpp6
-rw-r--r--src/video_core/gpu.h1
-rw-r--r--src/video_core/gpu_thread.cpp2
-rw-r--r--src/video_core/host_shaders/CMakeLists.txt1
-rw-r--r--src/video_core/host_shaders/opengl_convert_s8d24.comp18
-rw-r--r--src/video_core/renderer_base.cpp4
-rw-r--r--src/video_core/renderer_base.h3
-rw-r--r--src/video_core/renderer_opengl/gl_buffer_cache.cpp14
-rw-r--r--src/video_core/renderer_opengl/gl_buffer_cache.h12
-rw-r--r--src/video_core/renderer_opengl/gl_device.cpp10
-rw-r--r--src/video_core/renderer_opengl/gl_device.h7
-rw-r--r--src/video_core/renderer_opengl/gl_graphics_pipeline.cpp2
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp17
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.cpp4
-rw-r--r--src/video_core/renderer_opengl/gl_texture_cache.cpp57
-rw-r--r--src/video_core/renderer_opengl/gl_texture_cache.h16
-rw-r--r--src/video_core/renderer_opengl/maxwell_to_gl.h2
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.cpp15
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.h6
-rw-r--r--src/video_core/renderer_opengl/util_shaders.cpp24
-rw-r--r--src/video_core/renderer_opengl/util_shaders.h3
-rw-r--r--src/video_core/renderer_vulkan/blit_image.cpp7
-rw-r--r--src/video_core/renderer_vulkan/maxwell_to_vk.cpp2
-rw-r--r--src/video_core/renderer_vulkan/pipeline_statistics.cpp4
-rw-r--r--src/video_core/renderer_vulkan/vk_blit_screen.cpp15
-rw-r--r--src/video_core/renderer_vulkan/vk_buffer_cache.cpp12
-rw-r--r--src/video_core/renderer_vulkan/vk_buffer_cache.h6
-rw-r--r--src/video_core/renderer_vulkan/vk_compute_pipeline.cpp2
-rw-r--r--src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp2
-rw-r--r--src/video_core/renderer_vulkan/vk_pipeline_cache.cpp4
-rw-r--r--src/video_core/renderer_vulkan/vk_render_pass_cache.cpp2
-rw-r--r--src/video_core/renderer_vulkan/vk_scheduler.cpp6
-rw-r--r--src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp2
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.cpp61
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.h4
-rw-r--r--src/video_core/shader_cache.cpp2
-rw-r--r--src/video_core/surface.cpp8
-rw-r--r--src/video_core/surface.h10
-rw-r--r--src/video_core/texture_cache/format_lookup_table.cpp6
-rw-r--r--src/video_core/texture_cache/formatter.h4
-rw-r--r--src/video_core/texture_cache/image_base.h7
-rw-r--r--src/video_core/texture_cache/texture_cache.h64
-rw-r--r--src/video_core/texture_cache/texture_cache_base.h7
-rw-r--r--src/video_core/vulkan_common/vulkan_device.cpp70
-rw-r--r--src/video_core/vulkan_common/vulkan_device.h11
-rw-r--r--src/video_core/vulkan_common/vulkan_memory_allocator.cpp2
-rw-r--r--src/video_core/vulkan_common/vulkan_wrapper.cpp13
-rw-r--r--src/video_core/vulkan_common/vulkan_wrapper.h4
-rw-r--r--src/web_service/web_backend.cpp4
-rw-r--r--src/yuzu/CMakeLists.txt5
-rw-r--r--src/yuzu/aboutdialog.ui2
-rw-r--r--src/yuzu/applets/qt_web_browser.cpp3
-rw-r--r--src/yuzu/applets/qt_web_browser.h1
-rw-r--r--src/yuzu/bootmanager.cpp6
-rw-r--r--src/yuzu/compatdb.ui2
-rw-r--r--src/yuzu/configuration/config.cpp36
-rw-r--r--src/yuzu/configuration/config.h3
-rw-r--r--src/yuzu/configuration/configure_cpu.ui5
-rw-r--r--src/yuzu/configuration/configure_debug.cpp8
-rw-r--r--src/yuzu/configuration/configure_debug.ui93
-rw-r--r--src/yuzu/configuration/configure_dialog.cpp3
-rw-r--r--src/yuzu/configuration/configure_hotkeys.cpp7
-rw-r--r--src/yuzu/configuration/configure_input.cpp5
-rw-r--r--src/yuzu/configuration/configure_input_advanced.cpp9
-rw-r--r--src/yuzu/configuration/configure_input_advanced.h1
-rw-r--r--src/yuzu/configuration/configure_input_advanced.ui14
-rw-r--r--src/yuzu/configuration/configure_input_player.cpp22
-rw-r--r--src/yuzu/configuration/configure_per_game_addons.cpp6
-rw-r--r--src/yuzu/configuration/configure_ringcon.cpp424
-rw-r--r--src/yuzu/configuration/configure_ringcon.h85
-rw-r--r--src/yuzu/configuration/configure_ringcon.ui278
-rw-r--r--src/yuzu/game_list_p.h4
-rw-r--r--src/yuzu/main.cpp75
-rw-r--r--src/yuzu/main.h6
-rw-r--r--src/yuzu/uisettings.cpp8
-rw-r--r--src/yuzu/uisettings.h3
-rw-r--r--src/yuzu/util/controller_navigation.cpp2
-rw-r--r--src/yuzu_cmd/CMakeLists.txt2
-rw-r--r--src/yuzu_cmd/default_ini.h6
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2.cpp13
-rw-r--r--src/yuzu_cmd/yuzu.cpp1
275 files changed, 10492 insertions, 3318 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 1bdf70b76..9182dbfd4 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -65,12 +65,14 @@ if (MSVC)
/we4305 # 'context': truncation from 'type1' to 'type2'
/we4388 # 'expression': signed/unsigned mismatch
/we4389 # 'operator': signed/unsigned mismatch
+ /we4505 # 'function': unreferenced local function has been removed
/we4547 # 'operator': operator before comma has no effect; expected operator with side-effect
/we4549 # 'operator1': operator before comma has no effect; did you intend 'operator2'?
/we4555 # Expression has no effect; expected expression with side-effect
/we4715 # 'function': not all control paths return a value
/we4834 # Discarding return value of function with 'nodiscard' attribute
/we5038 # data member 'member1' will be initialized after data member 'member2'
+ /we5245 # 'function': unreferenced function with internal linkage has been removed
)
if (ARCHITECTURE_x86_64)
@@ -103,12 +105,6 @@ else()
-Wno-unused-parameter
)
- # TODO: Remove when we update to a GCC compiler that enables this
- # by default (i.e. GCC 10 or newer).
- if (CMAKE_CXX_COMPILER_ID STREQUAL GNU)
- add_compile_options(-fconcepts)
- endif()
-
if (ARCHITECTURE_x86_64)
add_compile_options("-mcx16")
endif()
diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt
index 090dd19b1..e553b8203 100644
--- a/src/audio_core/CMakeLists.txt
+++ b/src/audio_core/CMakeLists.txt
@@ -36,8 +36,6 @@ add_library(audio_core STATIC
splitter_context.h
stream.cpp
stream.h
- time_stretch.cpp
- time_stretch.h
voice_context.cpp
voice_context.h
@@ -63,7 +61,6 @@ if (NOT MSVC)
endif()
target_link_libraries(audio_core PUBLIC common core)
-target_link_libraries(audio_core PRIVATE SoundTouch)
if(ENABLE_CUBEB)
target_link_libraries(audio_core PRIVATE cubeb)
diff --git a/src/audio_core/cubeb_sink.cpp b/src/audio_core/cubeb_sink.cpp
index 93c35e785..13de3087c 100644
--- a/src/audio_core/cubeb_sink.cpp
+++ b/src/audio_core/cubeb_sink.cpp
@@ -7,7 +7,6 @@
#include <cstring>
#include "audio_core/cubeb_sink.h"
#include "audio_core/stream.h"
-#include "audio_core/time_stretch.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/ring_buffer.h"
@@ -23,8 +22,7 @@ class CubebSinkStream final : public SinkStream {
public:
CubebSinkStream(cubeb* ctx_, u32 sample_rate, u32 num_channels_, cubeb_devid output_device,
const std::string& name)
- : ctx{ctx_}, num_channels{std::min(num_channels_, 6u)}, time_stretch{sample_rate,
- num_channels} {
+ : ctx{ctx_}, num_channels{std::min(num_channels_, 6u)} {
cubeb_stream_params params{};
params.rate = sample_rate;
@@ -131,7 +129,6 @@ private:
Common::RingBuffer<s16, 0x10000> queue;
std::array<s16, 2> last_frame{};
std::atomic<bool> should_flush{};
- TimeStretcher time_stretch;
static long DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer,
void* output_buffer, long num_frames);
@@ -205,25 +202,7 @@ long CubebSinkStream::DataCallback([[maybe_unused]] cubeb_stream* stream, void*
const std::size_t num_channels = impl->GetNumChannels();
const std::size_t samples_to_write = num_channels * num_frames;
- std::size_t samples_written;
-
- /*
- if (Settings::values.enable_audio_stretching.GetValue()) {
- const std::vector<s16> in{impl->queue.Pop()};
- const std::size_t num_in{in.size() / num_channels};
- s16* const out{reinterpret_cast<s16*>(buffer)};
- const std::size_t out_frames =
- impl->time_stretch.Process(in.data(), num_in, out, num_frames);
- samples_written = out_frames * num_channels;
-
- if (impl->should_flush) {
- impl->time_stretch.Flush();
- impl->should_flush = false;
- }
- } else {
- samples_written = impl->queue.Pop(buffer, samples_to_write);
- }*/
- samples_written = impl->queue.Pop(buffer, samples_to_write);
+ const std::size_t samples_written = impl->queue.Pop(buffer, samples_to_write);
if (samples_written >= num_channels) {
std::memcpy(&impl->last_frame[0], buffer + (samples_written - num_channels) * sizeof(s16),
diff --git a/src/audio_core/sdl2_sink.cpp b/src/audio_core/sdl2_sink.cpp
index 62d3716a6..2d14ce2cb 100644
--- a/src/audio_core/sdl2_sink.cpp
+++ b/src/audio_core/sdl2_sink.cpp
@@ -7,7 +7,6 @@
#include <cstring>
#include "audio_core/sdl2_sink.h"
#include "audio_core/stream.h"
-#include "audio_core/time_stretch.h"
#include "common/assert.h"
#include "common/logging/log.h"
//#include "common/settings.h"
@@ -27,7 +26,7 @@ namespace AudioCore {
class SDLSinkStream final : public SinkStream {
public:
SDLSinkStream(u32 sample_rate, u32 num_channels_, const std::string& output_device)
- : num_channels{std::min(num_channels_, 6u)}, time_stretch{sample_rate, num_channels} {
+ : num_channels{std::min(num_channels_, 6u)} {
SDL_AudioSpec spec;
spec.freq = sample_rate;
@@ -116,7 +115,6 @@ private:
SDL_AudioDeviceID dev = 0;
u32 num_channels{};
std::atomic<bool> should_flush{};
- TimeStretcher time_stretch;
};
SDLSink::SDLSink(std::string_view target_device_name) {
diff --git a/src/audio_core/time_stretch.cpp b/src/audio_core/time_stretch.cpp
deleted file mode 100644
index 726591fce..000000000
--- a/src/audio_core/time_stretch.cpp
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <algorithm>
-#include <cmath>
-#include <cstddef>
-#include "audio_core/time_stretch.h"
-#include "common/logging/log.h"
-
-namespace AudioCore {
-
-TimeStretcher::TimeStretcher(u32 sample_rate, u32 channel_count) : m_sample_rate{sample_rate} {
- m_sound_touch.setChannels(channel_count);
- m_sound_touch.setSampleRate(sample_rate);
- m_sound_touch.setPitch(1.0);
- m_sound_touch.setTempo(1.0);
-}
-
-void TimeStretcher::Clear() {
- m_sound_touch.clear();
-}
-
-void TimeStretcher::Flush() {
- m_sound_touch.flush();
-}
-
-std::size_t TimeStretcher::Process(const s16* in, std::size_t num_in, s16* out,
- std::size_t num_out) {
- const double time_delta = static_cast<double>(num_out) / m_sample_rate; // seconds
-
- // We were given actual_samples number of samples, and num_samples were requested from us.
- double current_ratio = static_cast<double>(num_in) / static_cast<double>(num_out);
-
- const double max_latency = 0.25; // seconds
- const double max_backlog = m_sample_rate * max_latency;
- const double backlog_fullness = m_sound_touch.numSamples() / max_backlog;
- if (backlog_fullness > 4.0) {
- // Too many samples in backlog: Don't push anymore on
- num_in = 0;
- }
-
- // We ideally want the backlog to be about 50% full.
- // This gives some headroom both ways to prevent underflow and overflow.
- // We tweak current_ratio to encourage this.
- constexpr double tweak_time_scale = 0.05; // seconds
- const double tweak_correction = (backlog_fullness - 0.5) * (time_delta / tweak_time_scale);
- current_ratio *= std::pow(1.0 + 2.0 * tweak_correction, tweak_correction < 0 ? 3.0 : 1.0);
-
- // This low-pass filter smoothes out variance in the calculated stretch ratio.
- // The time-scale determines how responsive this filter is.
- constexpr double lpf_time_scale = 0.712; // seconds
- const double lpf_gain = 1.0 - std::exp(-time_delta / lpf_time_scale);
- m_stretch_ratio += lpf_gain * (current_ratio - m_stretch_ratio);
-
- // Place a lower limit of 5% speed. When a game boots up, there will be
- // many silence samples. These do not need to be timestretched.
- m_stretch_ratio = std::max(m_stretch_ratio, 0.05);
- m_sound_touch.setTempo(m_stretch_ratio);
-
- LOG_TRACE(Audio, "{:5}/{:5} ratio:{:0.6f} backlog:{:0.6f}", num_in, num_out, m_stretch_ratio,
- backlog_fullness);
-
- m_sound_touch.putSamples(in, static_cast<u32>(num_in));
- return m_sound_touch.receiveSamples(out, static_cast<u32>(num_out));
-}
-
-} // namespace AudioCore
diff --git a/src/audio_core/time_stretch.h b/src/audio_core/time_stretch.h
deleted file mode 100644
index bb2270b96..000000000
--- a/src/audio_core/time_stretch.h
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <cstddef>
-#include <SoundTouch.h>
-#include "common/common_types.h"
-
-namespace AudioCore {
-
-class TimeStretcher {
-public:
- TimeStretcher(u32 sample_rate, u32 channel_count);
-
- /// @param in Input sample buffer
- /// @param num_in Number of input frames in `in`
- /// @param out Output sample buffer
- /// @param num_out Desired number of output frames in `out`
- /// @returns Actual number of frames written to `out`
- std::size_t Process(const s16* in, std::size_t num_in, s16* out, std::size_t num_out);
-
- void Clear();
-
- void Flush();
-
-private:
- u32 m_sample_rate;
- soundtouch::SoundTouch m_sound_touch;
- double m_stretch_ratio = 1.0;
-};
-
-} // namespace AudioCore
diff --git a/src/common/atomic_ops.h b/src/common/atomic_ops.h
index b94d73c7a..69fde8421 100644
--- a/src/common/atomic_ops.h
+++ b/src/common/atomic_ops.h
@@ -46,6 +46,50 @@ namespace Common {
reinterpret_cast<__int64*>(expected.data())) != 0;
}
+[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u8* pointer, u8 value, u8 expected,
+ u8& actual) {
+ actual =
+ _InterlockedCompareExchange8(reinterpret_cast<volatile char*>(pointer), value, expected);
+ return actual == expected;
+}
+
+[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u16* pointer, u16 value, u16 expected,
+ u16& actual) {
+ actual =
+ _InterlockedCompareExchange16(reinterpret_cast<volatile short*>(pointer), value, expected);
+ return actual == expected;
+}
+
+[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u32* pointer, u32 value, u32 expected,
+ u32& actual) {
+ actual =
+ _InterlockedCompareExchange(reinterpret_cast<volatile long*>(pointer), value, expected);
+ return actual == expected;
+}
+
+[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u64* pointer, u64 value, u64 expected,
+ u64& actual) {
+ actual = _InterlockedCompareExchange64(reinterpret_cast<volatile __int64*>(pointer), value,
+ expected);
+ return actual == expected;
+}
+
+[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u64* pointer, u128 value, u128 expected,
+ u128& actual) {
+ const bool result =
+ _InterlockedCompareExchange128(reinterpret_cast<volatile __int64*>(pointer), value[1],
+ value[0], reinterpret_cast<__int64*>(expected.data())) != 0;
+ actual = expected;
+ return result;
+}
+
+[[nodiscard]] inline u128 AtomicLoad128(volatile u64* pointer) {
+ u128 result{};
+ _InterlockedCompareExchange128(reinterpret_cast<volatile __int64*>(pointer), result[1],
+ result[0], reinterpret_cast<__int64*>(result.data()));
+ return result;
+}
+
#else
[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u8* pointer, u8 value, u8 expected) {
@@ -72,6 +116,52 @@ namespace Common {
return __sync_bool_compare_and_swap((unsigned __int128*)pointer, expected_a, value_a);
}
+[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u8* pointer, u8 value, u8 expected,
+ u8& actual) {
+ actual = __sync_val_compare_and_swap(pointer, expected, value);
+ return actual == expected;
+}
+
+[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u16* pointer, u16 value, u16 expected,
+ u16& actual) {
+ actual = __sync_val_compare_and_swap(pointer, expected, value);
+ return actual == expected;
+}
+
+[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u32* pointer, u32 value, u32 expected,
+ u32& actual) {
+ actual = __sync_val_compare_and_swap(pointer, expected, value);
+ return actual == expected;
+}
+
+[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u64* pointer, u64 value, u64 expected,
+ u64& actual) {
+ actual = __sync_val_compare_and_swap(pointer, expected, value);
+ return actual == expected;
+}
+
+[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u64* pointer, u128 value, u128 expected,
+ u128& actual) {
+ unsigned __int128 value_a;
+ unsigned __int128 expected_a;
+ unsigned __int128 actual_a;
+ std::memcpy(&value_a, value.data(), sizeof(u128));
+ std::memcpy(&expected_a, expected.data(), sizeof(u128));
+ actual_a = __sync_val_compare_and_swap((unsigned __int128*)pointer, expected_a, value_a);
+ std::memcpy(actual.data(), &actual_a, sizeof(u128));
+ return actual_a == expected_a;
+}
+
+[[nodiscard]] inline u128 AtomicLoad128(volatile u64* pointer) {
+ unsigned __int128 zeros_a = 0;
+ unsigned __int128 result_a =
+ __sync_val_compare_and_swap((unsigned __int128*)pointer, zeros_a, zeros_a);
+
+ u128 result;
+ std::memcpy(result.data(), &result_a, sizeof(u128));
+ return result;
+}
+
#endif
} // namespace Common
diff --git a/src/common/fiber.cpp b/src/common/fiber.cpp
index 81b212e4b..177a74deb 100644
--- a/src/common/fiber.cpp
+++ b/src/common/fiber.cpp
@@ -2,9 +2,10 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <mutex>
+
#include "common/assert.h"
#include "common/fiber.h"
-#include "common/spin_lock.h"
#include "common/virtual_buffer.h"
#include <boost/context/detail/fcontext.hpp>
@@ -19,7 +20,7 @@ struct Fiber::FiberImpl {
VirtualBuffer<u8> stack;
VirtualBuffer<u8> rewind_stack;
- SpinLock guard{};
+ std::mutex guard;
std::function<void(void*)> entry_point;
std::function<void(void*)> rewind_point;
void* rewind_parameter{};
diff --git a/src/common/host_memory.cpp b/src/common/host_memory.cpp
index e829af1ac..802943eb7 100644
--- a/src/common/host_memory.cpp
+++ b/src/common/host_memory.cpp
@@ -149,7 +149,7 @@ public:
}
void Unmap(size_t virtual_offset, size_t length) {
- std::lock_guard lock{placeholder_mutex};
+ std::scoped_lock lock{placeholder_mutex};
// Unmap until there are no more placeholders
while (UnmapOnePlaceholder(virtual_offset, length)) {
@@ -169,7 +169,7 @@ public:
}
const size_t virtual_end = virtual_offset + length;
- std::lock_guard lock{placeholder_mutex};
+ std::scoped_lock lock{placeholder_mutex};
auto [it, end] = placeholders.equal_range({virtual_offset, virtual_end});
while (it != end) {
const size_t offset = std::max(it->lower(), virtual_offset);
diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp
index 4afc1369a..4acbff649 100644
--- a/src/common/logging/filter.cpp
+++ b/src/common/logging/filter.cpp
@@ -101,6 +101,7 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
SUB(Service, GRC) \
SUB(Service, HID) \
SUB(Service, IRS) \
+ SUB(Service, JIT) \
SUB(Service, LBL) \
SUB(Service, LDN) \
SUB(Service, LDR) \
@@ -119,6 +120,7 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
SUB(Service, NPNS) \
SUB(Service, NS) \
SUB(Service, NVDRV) \
+ SUB(Service, NVFlinger) \
SUB(Service, OLSC) \
SUB(Service, PCIE) \
SUB(Service, PCTL) \
diff --git a/src/common/logging/types.h b/src/common/logging/types.h
index 2b6e4daa7..99c15fa96 100644
--- a/src/common/logging/types.h
+++ b/src/common/logging/types.h
@@ -69,6 +69,7 @@ enum class Class : u8 {
Service_GRC, ///< The game recording service
Service_HID, ///< The HID (Human interface device) service
Service_IRS, ///< The IRS service
+ Service_JIT, ///< The JIT service
Service_LBL, ///< The LBL (LCD backlight) service
Service_LDN, ///< The LDN (Local domain network) service
Service_LDR, ///< The loader service
@@ -87,6 +88,7 @@ enum class Class : u8 {
Service_NPNS, ///< The NPNS service
Service_NS, ///< The NS services
Service_NVDRV, ///< The NVDRV (Nvidia driver) service
+ Service_NVFlinger, ///< The NVFlinger service
Service_OLSC, ///< The OLSC service
Service_PCIE, ///< The PCIe service
Service_PCTL, ///< The PCTL (Parental control) service
diff --git a/src/common/math_util.h b/src/common/math_util.h
index 510c4e56d..54485bf53 100644
--- a/src/common/math_util.h
+++ b/src/common/math_util.h
@@ -4,6 +4,7 @@
#pragma once
+#include <algorithm>
#include <cstdlib>
#include <type_traits>
@@ -20,10 +21,32 @@ struct Rectangle {
constexpr Rectangle() = default;
+ constexpr Rectangle(T width, T height) : right(width), bottom(height) {}
+
constexpr Rectangle(T left_, T top_, T right_, T bottom_)
: left(left_), top(top_), right(right_), bottom(bottom_) {}
- [[nodiscard]] T GetWidth() const {
+ [[nodiscard]] constexpr T Left() const {
+ return left;
+ }
+
+ [[nodiscard]] constexpr T Top() const {
+ return top;
+ }
+
+ [[nodiscard]] constexpr T Right() const {
+ return right;
+ }
+
+ [[nodiscard]] constexpr T Bottom() const {
+ return bottom;
+ }
+
+ [[nodiscard]] constexpr bool IsEmpty() const {
+ return (GetWidth() <= 0) || (GetHeight() <= 0);
+ }
+
+ [[nodiscard]] constexpr T GetWidth() const {
if constexpr (std::is_floating_point_v<T>) {
return std::abs(right - left);
} else {
@@ -31,7 +54,7 @@ struct Rectangle {
}
}
- [[nodiscard]] T GetHeight() const {
+ [[nodiscard]] constexpr T GetHeight() const {
if constexpr (std::is_floating_point_v<T>) {
return std::abs(bottom - top);
} else {
@@ -39,18 +62,35 @@ struct Rectangle {
}
}
- [[nodiscard]] Rectangle<T> TranslateX(const T x) const {
+ [[nodiscard]] constexpr Rectangle<T> TranslateX(const T x) const {
return Rectangle{left + x, top, right + x, bottom};
}
- [[nodiscard]] Rectangle<T> TranslateY(const T y) const {
+ [[nodiscard]] constexpr Rectangle<T> TranslateY(const T y) const {
return Rectangle{left, top + y, right, bottom + y};
}
- [[nodiscard]] Rectangle<T> Scale(const float s) const {
+ [[nodiscard]] constexpr Rectangle<T> Scale(const float s) const {
return Rectangle{left, top, static_cast<T>(static_cast<float>(left + GetWidth()) * s),
static_cast<T>(static_cast<float>(top + GetHeight()) * s)};
}
+
+ [[nodiscard]] constexpr bool operator==(const Rectangle<T>& rhs) const {
+ return (left == rhs.left) && (top == rhs.top) && (right == rhs.right) &&
+ (bottom == rhs.bottom);
+ }
+
+ [[nodiscard]] constexpr bool operator!=(const Rectangle<T>& rhs) const {
+ return !operator==(rhs);
+ }
+
+ [[nodiscard]] constexpr bool Intersect(const Rectangle<T>& with, Rectangle<T>* result) const {
+ result->left = std::max(left, with.left);
+ result->top = std::max(top, with.top);
+ result->right = std::min(right, with.right);
+ result->bottom = std::min(bottom, with.bottom);
+ return !result->IsEmpty();
+ }
};
template <typename T>
diff --git a/src/common/settings.h b/src/common/settings.h
index a37d83fb3..3b7be63b3 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -38,6 +38,7 @@ enum class CPUAccuracy : u32 {
Auto = 0,
Accurate = 1,
Unsafe = 2,
+ Paranoid = 3,
};
enum class FullscreenMode : u32 {
@@ -470,7 +471,7 @@ struct Values {
// Cpu
RangedSetting<CPUAccuracy> cpu_accuracy{CPUAccuracy::Auto, CPUAccuracy::Auto,
- CPUAccuracy::Unsafe, "cpu_accuracy"};
+ CPUAccuracy::Paranoid, "cpu_accuracy"};
// TODO: remove cpu_accuracy_first_time, migration setting added 8 July 2021
BasicSetting<bool> cpu_accuracy_first_time{true, "cpu_accuracy_first_time"};
BasicSetting<bool> cpu_debug_mode{false, "cpu_debug_mode"};
@@ -589,6 +590,9 @@ struct Values {
BasicSetting<int> touch_from_button_map_index{0, "touch_from_button_map"};
std::vector<TouchFromButtonMap> touch_from_button_maps;
+ BasicSetting<bool> enable_ring_controller{true, "enable_ring_controller"};
+ RingconRaw ringcon_analogs;
+
// Data Storage
BasicSetting<bool> use_virtual_sd{true, "use_virtual_sd"};
BasicSetting<bool> gamecard_inserted{false, "gamecard_inserted"};
diff --git a/src/common/settings_input.h b/src/common/settings_input.h
index 4ff37e186..6f42346bc 100644
--- a/src/common/settings_input.h
+++ b/src/common/settings_input.h
@@ -357,6 +357,7 @@ constexpr int NUM_KEYBOARD_MODS_HID = NumKeyboardMods;
using AnalogsRaw = std::array<std::string, NativeAnalog::NumAnalogs>;
using ButtonsRaw = std::array<std::string, NativeButton::NumButtons>;
using MotionsRaw = std::array<std::string, NativeMotion::NumMotions>;
+using RingconRaw = std::string;
constexpr u32 JOYCON_BODY_NEON_RED = 0xFF3C28;
constexpr u32 JOYCON_BUTTONS_NEON_RED = 0x1E0A0A;
diff --git a/src/common/thread.h b/src/common/thread.h
index a8c17c71a..626609372 100644
--- a/src/common/thread.h
+++ b/src/common/thread.h
@@ -17,7 +17,7 @@ namespace Common {
class Event {
public:
void Set() {
- std::lock_guard lk{mutex};
+ std::scoped_lock lk{mutex};
if (!is_set) {
is_set = true;
condvar.notify_one();
diff --git a/src/common/threadsafe_queue.h b/src/common/threadsafe_queue.h
index 2c8c2b90e..7272ac6e8 100644
--- a/src/common/threadsafe_queue.h
+++ b/src/common/threadsafe_queue.h
@@ -52,7 +52,7 @@ public:
// line before cv.wait
// TODO(bunnei): This can be replaced with C++20 waitable atomics when properly supported.
// See discussion on https://github.com/yuzu-emu/yuzu/pull/3173 for details.
- std::lock_guard lock{cv_mutex};
+ std::scoped_lock lock{cv_mutex};
cv.notify_one();
}
@@ -159,7 +159,7 @@ public:
template <typename Arg>
void Push(Arg&& t) {
- std::lock_guard lock{write_lock};
+ std::scoped_lock lock{write_lock};
spsc_queue.Push(t);
}
diff --git a/src/common/x64/native_clock.cpp b/src/common/x64/native_clock.cpp
index 347e41efc..7fd9d22f8 100644
--- a/src/common/x64/native_clock.cpp
+++ b/src/common/x64/native_clock.cpp
@@ -10,25 +10,49 @@
#include "common/uint128.h"
#include "common/x64/native_clock.h"
+#ifdef _MSC_VER
+#include <intrin.h>
+#endif
+
namespace Common {
+#ifdef _MSC_VER
+__forceinline static u64 FencedRDTSC() {
+ _mm_lfence();
+ _ReadWriteBarrier();
+ const u64 result = __rdtsc();
+ _mm_lfence();
+ _ReadWriteBarrier();
+ return result;
+}
+#else
+static u64 FencedRDTSC() {
+ u64 result;
+ asm volatile("lfence\n\t"
+ "rdtsc\n\t"
+ "shl $32, %%rdx\n\t"
+ "or %%rdx, %0\n\t"
+ "lfence"
+ : "=a"(result)
+ :
+ : "rdx", "memory", "cc");
+ return result;
+}
+#endif
+
u64 EstimateRDTSCFrequency() {
// Discard the first result measuring the rdtsc.
- _mm_mfence();
- __rdtsc();
+ FencedRDTSC();
std::this_thread::sleep_for(std::chrono::milliseconds{1});
- _mm_mfence();
- __rdtsc();
+ FencedRDTSC();
// Get the current time.
const auto start_time = std::chrono::steady_clock::now();
- _mm_mfence();
- const u64 tsc_start = __rdtsc();
+ const u64 tsc_start = FencedRDTSC();
// Wait for 200 milliseconds.
std::this_thread::sleep_for(std::chrono::milliseconds{200});
const auto end_time = std::chrono::steady_clock::now();
- _mm_mfence();
- const u64 tsc_end = __rdtsc();
+ const u64 tsc_end = FencedRDTSC();
// Calculate differences.
const u64 timer_diff = static_cast<u64>(
std::chrono::duration_cast<std::chrono::nanoseconds>(end_time - start_time).count());
@@ -42,8 +66,7 @@ NativeClock::NativeClock(u64 emulated_cpu_frequency_, u64 emulated_clock_frequen
u64 rtsc_frequency_)
: WallClock(emulated_cpu_frequency_, emulated_clock_frequency_, true), rtsc_frequency{
rtsc_frequency_} {
- _mm_mfence();
- time_point.inner.last_measure = __rdtsc();
+ time_point.inner.last_measure = FencedRDTSC();
time_point.inner.accumulated_ticks = 0U;
ns_rtsc_factor = GetFixedPoint64Factor(NS_RATIO, rtsc_frequency);
us_rtsc_factor = GetFixedPoint64Factor(US_RATIO, rtsc_frequency);
@@ -55,10 +78,10 @@ NativeClock::NativeClock(u64 emulated_cpu_frequency_, u64 emulated_clock_frequen
u64 NativeClock::GetRTSC() {
TimePoint new_time_point{};
TimePoint current_time_point{};
+
+ current_time_point.pack = Common::AtomicLoad128(time_point.pack.data());
do {
- current_time_point.pack = time_point.pack;
- _mm_mfence();
- const u64 current_measure = __rdtsc();
+ const u64 current_measure = FencedRDTSC();
u64 diff = current_measure - current_time_point.inner.last_measure;
diff = diff & ~static_cast<u64>(static_cast<s64>(diff) >> 63); // max(diff, 0)
new_time_point.inner.last_measure = current_measure > current_time_point.inner.last_measure
@@ -66,7 +89,7 @@ u64 NativeClock::GetRTSC() {
: current_time_point.inner.last_measure;
new_time_point.inner.accumulated_ticks = current_time_point.inner.accumulated_ticks + diff;
} while (!Common::AtomicCompareAndSwap(time_point.pack.data(), new_time_point.pack,
- current_time_point.pack));
+ current_time_point.pack, current_time_point.pack));
/// The clock cannot be more precise than the guest timer, remove the lower bits
return new_time_point.inner.accumulated_ticks & inaccuracy_mask;
}
@@ -75,13 +98,13 @@ void NativeClock::Pause(bool is_paused) {
if (!is_paused) {
TimePoint current_time_point{};
TimePoint new_time_point{};
+
+ current_time_point.pack = Common::AtomicLoad128(time_point.pack.data());
do {
- current_time_point.pack = time_point.pack;
new_time_point.pack = current_time_point.pack;
- _mm_mfence();
- new_time_point.inner.last_measure = __rdtsc();
+ new_time_point.inner.last_measure = FencedRDTSC();
} while (!Common::AtomicCompareAndSwap(time_point.pack.data(), new_time_point.pack,
- current_time_point.pack));
+ current_time_point.pack, current_time_point.pack));
}
}
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 1d4e92edb..62230bae0 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -13,6 +13,8 @@ add_library(core STATIC
arm/dynarmic/arm_exclusive_monitor.h
arm/exclusive_monitor.cpp
arm/exclusive_monitor.h
+ arm/symbols.cpp
+ arm/symbols.h
constants.cpp
constants.h
core.cpp
@@ -122,8 +124,8 @@ add_library(core STATIC
frontend/applets/error.h
frontend/applets/general_frontend.cpp
frontend/applets/general_frontend.h
- frontend/applets/mii.cpp
- frontend/applets/mii.h
+ frontend/applets/mii_edit.cpp
+ frontend/applets/mii_edit.h
frontend/applets/profile_select.cpp
frontend/applets/profile_select.h
frontend/applets/software_keyboard.cpp
@@ -306,8 +308,9 @@ add_library(core STATIC
hle/service/am/applets/applet_error.h
hle/service/am/applets/applet_general_backend.cpp
hle/service/am/applets/applet_general_backend.h
- hle/service/am/applets/applet_mii.cpp
- hle/service/am/applets/applet_mii.h
+ hle/service/am/applets/applet_mii_edit.cpp
+ hle/service/am/applets/applet_mii_edit.h
+ hle/service/am/applets/applet_mii_edit_types.h
hle/service/am/applets/applet_profile_select.cpp
hle/service/am/applets/applet_profile_select.h
hle/service/am/applets/applet_software_keyboard.cpp
@@ -431,6 +434,8 @@ add_library(core STATIC
hle/service/grc/grc.h
hle/service/hid/hid.cpp
hle/service/hid/hid.h
+ hle/service/hid/hidbus.cpp
+ hle/service/hid/hidbus.h
hle/service/hid/irs.cpp
hle/service/hid/irs.h
hle/service/hid/ring_lifo.h
@@ -457,6 +462,18 @@ add_library(core STATIC
hle/service/hid/controllers/touchscreen.h
hle/service/hid/controllers/xpad.cpp
hle/service/hid/controllers/xpad.h
+ hle/service/hid/hidbus/hidbus_base.cpp
+ hle/service/hid/hidbus/hidbus_base.h
+ hle/service/hid/hidbus/ringcon.cpp
+ hle/service/hid/hidbus/ringcon.h
+ hle/service/hid/hidbus/starlink.cpp
+ hle/service/hid/hidbus/starlink.h
+ hle/service/hid/hidbus/stubbed.cpp
+ hle/service/hid/hidbus/stubbed.h
+ hle/service/jit/jit_context.cpp
+ hle/service/jit/jit_context.h
+ hle/service/jit/jit.cpp
+ hle/service/jit/jit.h
hle/service/lbl/lbl.cpp
hle/service/lbl/lbl.h
hle/service/ldn/errors.h
@@ -534,10 +551,35 @@ add_library(core STATIC
hle/service/nvdrv/nvmemp.h
hle/service/nvdrv/syncpoint_manager.cpp
hle/service/nvdrv/syncpoint_manager.h
- hle/service/nvflinger/buffer_queue.cpp
- hle/service/nvflinger/buffer_queue.h
+ hle/service/nvflinger/binder.h
+ hle/service/nvflinger/buffer_item.h
+ hle/service/nvflinger/buffer_item_consumer.cpp
+ hle/service/nvflinger/buffer_item_consumer.h
+ hle/service/nvflinger/buffer_queue_consumer.cpp
+ hle/service/nvflinger/buffer_queue_consumer.h
+ hle/service/nvflinger/buffer_queue_core.cpp
+ hle/service/nvflinger/buffer_queue_core.h
+ hle/service/nvflinger/buffer_queue_defs.h
+ hle/service/nvflinger/buffer_queue_producer.cpp
+ hle/service/nvflinger/buffer_queue_producer.h
+ hle/service/nvflinger/buffer_slot.h
+ hle/service/nvflinger/buffer_transform_flags.h
+ hle/service/nvflinger/consumer_base.cpp
+ hle/service/nvflinger/consumer_base.h
+ hle/service/nvflinger/consumer_listener.h
+ hle/service/nvflinger/graphic_buffer_producer.cpp
+ hle/service/nvflinger/graphic_buffer_producer.h
+ hle/service/nvflinger/hos_binder_driver_server.cpp
+ hle/service/nvflinger/hos_binder_driver_server.h
hle/service/nvflinger/nvflinger.cpp
hle/service/nvflinger/nvflinger.h
+ hle/service/nvflinger/parcel.h
+ hle/service/nvflinger/pixel_format.h
+ hle/service/nvflinger/producer_listener.h
+ hle/service/nvflinger/status.h
+ hle/service/nvflinger/ui/fence.h
+ hle/service/nvflinger/ui/graphic_buffer.h
+ hle/service/nvflinger/window.h
hle/service/olsc/olsc.cpp
hle/service/olsc/olsc.h
hle/service/pcie/pcie.cpp
diff --git a/src/core/arm/arm_interface.cpp b/src/core/arm/arm_interface.cpp
index 0951e1976..2a9390e26 100644
--- a/src/core/arm/arm_interface.cpp
+++ b/src/core/arm/arm_interface.cpp
@@ -8,226 +8,41 @@
#include "common/common_types.h"
#include "common/logging/log.h"
#include "core/arm/arm_interface.h"
+#include "core/arm/symbols.h"
#include "core/core.h"
+#include "core/hle/kernel/k_process.h"
#include "core/loader/loader.h"
#include "core/memory.h"
-namespace Core {
-namespace {
-
-constexpr u64 ELF_DYNAMIC_TAG_NULL = 0;
-constexpr u64 ELF_DYNAMIC_TAG_STRTAB = 5;
-constexpr u64 ELF_DYNAMIC_TAG_SYMTAB = 6;
-constexpr u64 ELF_DYNAMIC_TAG_SYMENT = 11;
-
-enum class ELFSymbolType : u8 {
- None = 0,
- Object = 1,
- Function = 2,
- Section = 3,
- File = 4,
- Common = 5,
- TLS = 6,
-};
-
-enum class ELFSymbolBinding : u8 {
- Local = 0,
- Global = 1,
- Weak = 2,
-};
-
-enum class ELFSymbolVisibility : u8 {
- Default = 0,
- Internal = 1,
- Hidden = 2,
- Protected = 3,
-};
-
-struct ELFSymbol {
- u32 name_index;
- union {
- u8 info;
-
- BitField<0, 4, ELFSymbolType> type;
- BitField<4, 4, ELFSymbolBinding> binding;
- };
- ELFSymbolVisibility visibility;
- u16 sh_index;
- u64 value;
- u64 size;
-};
-static_assert(sizeof(ELFSymbol) == 0x18, "ELFSymbol has incorrect size.");
-
-using Symbols = std::vector<std::pair<ELFSymbol, std::string>>;
-
-Symbols GetSymbols(VAddr text_offset, Core::Memory::Memory& memory) {
- const auto mod_offset = text_offset + memory.Read32(text_offset + 4);
-
- if (mod_offset < text_offset || (mod_offset & 0b11) != 0 ||
- memory.Read32(mod_offset) != Common::MakeMagic('M', 'O', 'D', '0')) {
- return {};
- }
-
- const auto dynamic_offset = memory.Read32(mod_offset + 0x4) + mod_offset;
-
- VAddr string_table_offset{};
- VAddr symbol_table_offset{};
- u64 symbol_entry_size{};
-
- VAddr dynamic_index = dynamic_offset;
- while (true) {
- const u64 tag = memory.Read64(dynamic_index);
- const u64 value = memory.Read64(dynamic_index + 0x8);
- dynamic_index += 0x10;
-
- if (tag == ELF_DYNAMIC_TAG_NULL) {
- break;
- }
-
- if (tag == ELF_DYNAMIC_TAG_STRTAB) {
- string_table_offset = value;
- } else if (tag == ELF_DYNAMIC_TAG_SYMTAB) {
- symbol_table_offset = value;
- } else if (tag == ELF_DYNAMIC_TAG_SYMENT) {
- symbol_entry_size = value;
- }
- }
-
- if (string_table_offset == 0 || symbol_table_offset == 0 || symbol_entry_size == 0) {
- return {};
- }
+#include "core/arm/dynarmic/arm_dynarmic_32.h"
+#include "core/arm/dynarmic/arm_dynarmic_64.h"
- const auto string_table_address = text_offset + string_table_offset;
- const auto symbol_table_address = text_offset + symbol_table_offset;
-
- Symbols out;
-
- VAddr symbol_index = symbol_table_address;
- while (symbol_index < string_table_address) {
- ELFSymbol symbol{};
- memory.ReadBlock(symbol_index, &symbol, sizeof(ELFSymbol));
-
- VAddr string_offset = string_table_address + symbol.name_index;
- std::string name;
- for (u8 c = memory.Read8(string_offset); c != 0; c = memory.Read8(++string_offset)) {
- name += static_cast<char>(c);
- }
-
- symbol_index += symbol_entry_size;
- out.push_back({symbol, name});
- }
-
- return out;
-}
-
-std::optional<std::string> GetSymbolName(const Symbols& symbols, VAddr func_address) {
- const auto iter =
- std::find_if(symbols.begin(), symbols.end(), [func_address](const auto& pair) {
- const auto& symbol = pair.first;
- const auto end_address = symbol.value + symbol.size;
- return func_address >= symbol.value && func_address < end_address;
- });
-
- if (iter == symbols.end()) {
- return std::nullopt;
- }
-
- return iter->second;
-}
-
-} // Anonymous namespace
+namespace Core {
constexpr u64 SEGMENT_BASE = 0x7100000000ull;
std::vector<ARM_Interface::BacktraceEntry> ARM_Interface::GetBacktraceFromContext(
- System& system, const ThreadContext64& ctx) {
- std::vector<BacktraceEntry> out;
- auto& memory = system.Memory();
-
- auto fp = ctx.cpu_registers[29];
- auto lr = ctx.cpu_registers[30];
- while (true) {
- out.push_back({
- .module = "",
- .address = 0,
- .original_address = lr,
- .offset = 0,
- .name = {},
- });
-
- if (fp == 0) {
- break;
- }
-
- lr = memory.Read64(fp + 8) - 4;
- fp = memory.Read64(fp);
- }
-
- std::map<VAddr, std::string> modules;
- auto& loader{system.GetAppLoader()};
- if (loader.ReadNSOModules(modules) != Loader::ResultStatus::Success) {
- return {};
- }
-
- std::map<std::string, Symbols> symbols;
- for (const auto& module : modules) {
- symbols.insert_or_assign(module.second, GetSymbols(module.first, memory));
- }
-
- for (auto& entry : out) {
- VAddr base = 0;
- for (auto iter = modules.rbegin(); iter != modules.rend(); ++iter) {
- const auto& module{*iter};
- if (entry.original_address >= module.first) {
- entry.module = module.second;
- base = module.first;
- break;
- }
- }
-
- entry.offset = entry.original_address - base;
- entry.address = SEGMENT_BASE + entry.offset;
-
- if (entry.module.empty())
- entry.module = "unknown";
-
- const auto symbol_set = symbols.find(entry.module);
- if (symbol_set != symbols.end()) {
- const auto symbol = GetSymbolName(symbol_set->second, entry.offset);
- if (symbol.has_value()) {
- // TODO(DarkLordZach): Add demangling of symbol names.
- entry.name = *symbol;
- }
- }
- }
-
- return out;
+ Core::System& system, const ARM_Interface::ThreadContext32& ctx) {
+ return ARM_Dynarmic_32::GetBacktraceFromContext(system, ctx);
}
-std::vector<ARM_Interface::BacktraceEntry> ARM_Interface::GetBacktrace() const {
- std::vector<BacktraceEntry> out;
- auto& memory = system.Memory();
-
- auto fp = GetReg(29);
- auto lr = GetReg(30);
- while (true) {
- out.push_back({"", 0, lr, 0, ""});
- if (!fp) {
- break;
- }
- lr = memory.Read64(fp + 8) - 4;
- fp = memory.Read64(fp);
- }
+std::vector<ARM_Interface::BacktraceEntry> ARM_Interface::GetBacktraceFromContext(
+ Core::System& system, const ARM_Interface::ThreadContext64& ctx) {
+ return ARM_Dynarmic_64::GetBacktraceFromContext(system, ctx);
+}
+void ARM_Interface::SymbolicateBacktrace(Core::System& system, std::vector<BacktraceEntry>& out) {
std::map<VAddr, std::string> modules;
auto& loader{system.GetAppLoader()};
if (loader.ReadNSOModules(modules) != Loader::ResultStatus::Success) {
- return {};
+ return;
}
- std::map<std::string, Symbols> symbols;
+ std::map<std::string, Symbols::Symbols> symbols;
for (const auto& module : modules) {
- symbols.insert_or_assign(module.second, GetSymbols(module.first, memory));
+ symbols.insert_or_assign(module.second,
+ Symbols::GetSymbols(module.first, system.Memory(),
+ system.CurrentProcess()->Is64BitProcess()));
}
for (auto& entry : out) {
@@ -244,24 +59,23 @@ std::vector<ARM_Interface::BacktraceEntry> ARM_Interface::GetBacktrace() const {
entry.offset = entry.original_address - base;
entry.address = SEGMENT_BASE + entry.offset;
- if (entry.module.empty())
+ if (entry.module.empty()) {
entry.module = "unknown";
+ }
const auto symbol_set = symbols.find(entry.module);
if (symbol_set != symbols.end()) {
- const auto symbol = GetSymbolName(symbol_set->second, entry.offset);
+ const auto symbol = Symbols::GetSymbolName(symbol_set->second, entry.offset);
if (symbol.has_value()) {
// TODO(DarkLordZach): Add demangling of symbol names.
entry.name = *symbol;
}
}
}
-
- return out;
}
void ARM_Interface::LogBacktrace() const {
- const VAddr sp = GetReg(13);
+ const VAddr sp = GetSP();
const VAddr pc = GetPC();
LOG_ERROR(Core_ARM, "Backtrace, sp={:016X}, pc={:016X}", sp, pc);
LOG_ERROR(Core_ARM, "{:20}{:20}{:20}{:20}{}", "Module Name", "Address", "Original Address",
diff --git a/src/core/arm/arm_interface.h b/src/core/arm/arm_interface.h
index c60322442..bcec4b3b8 100644
--- a/src/core/arm/arm_interface.h
+++ b/src/core/arm/arm_interface.h
@@ -101,6 +101,12 @@ public:
virtual u64 GetPC() const = 0;
/**
+ * Get the current Stack Pointer
+ * @return Returns current SP
+ */
+ virtual u64 GetSP() const = 0;
+
+ /**
* Get an ARM register
* @param index Register index
* @return Returns the value in the register
@@ -171,6 +177,9 @@ public:
/// Prepare core for thread reschedule (if needed to correctly handle state)
virtual void PrepareReschedule() = 0;
+ /// Signal an interrupt and ask the core to halt as soon as possible.
+ virtual void SignalInterrupt() = 0;
+
struct BacktraceEntry {
std::string module;
u64 address;
@@ -180,16 +189,12 @@ public:
};
static std::vector<BacktraceEntry> GetBacktraceFromContext(System& system,
+ const ThreadContext32& ctx);
+ static std::vector<BacktraceEntry> GetBacktraceFromContext(System& system,
const ThreadContext64& ctx);
- std::vector<BacktraceEntry> GetBacktrace() const;
+ virtual std::vector<BacktraceEntry> GetBacktrace() const = 0;
- /// fp (= r29) points to the last frame record.
- /// Note that this is the frame record for the *previous* frame, not the current one.
- /// Note we need to subtract 4 from our last read to get the proper address
- /// Frame records are two words long:
- /// fp+0 : pointer to previous frame record
- /// fp+8 : value of lr for frame
void LogBacktrace() const;
protected:
@@ -197,6 +202,8 @@ protected:
System& system;
CPUInterrupts& interrupt_handlers;
bool uses_wall_clock;
+
+ static void SymbolicateBacktrace(Core::System& system, std::vector<BacktraceEntry>& out);
};
} // namespace Core
diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.cpp b/src/core/arm/dynarmic/arm_dynarmic_32.cpp
index c1c843b8f..da5659046 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_32.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic_32.cpp
@@ -25,6 +25,9 @@ namespace Core {
using namespace Common::Literals;
+constexpr Dynarmic::HaltReason break_loop = Dynarmic::HaltReason::UserDefined2;
+constexpr Dynarmic::HaltReason svc_call = Dynarmic::HaltReason::UserDefined3;
+
class DynarmicCallbacks32 : public Dynarmic::A32::UserCallbacks {
public:
explicit DynarmicCallbacks32(ARM_Dynarmic_32& parent_)
@@ -70,11 +73,13 @@ public:
}
void InterpreterFallback(u32 pc, std::size_t num_instructions) override {
+ parent.LogBacktrace();
UNIMPLEMENTED_MSG("This should never happen, pc = {:08X}, code = {:08X}", pc,
MemoryReadCode(pc));
}
void ExceptionRaised(u32 pc, Dynarmic::A32::Exception exception) override {
+ parent.LogBacktrace();
LOG_CRITICAL(Core_ARM,
"ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X}, thumb = {})",
exception, pc, MemoryReadCode(pc), parent.IsInThumbMode());
@@ -82,15 +87,13 @@ public:
}
void CallSVC(u32 swi) override {
- parent.svc_called = true;
parent.svc_swi = swi;
- parent.jit->HaltExecution();
+ parent.jit.load()->HaltExecution(svc_call);
}
void AddTicks(u64 ticks) override {
- if (parent.uses_wall_clock) {
- return;
- }
+ ASSERT_MSG(!parent.uses_wall_clock, "This should never happen - dynarmic ticking disabled");
+
// Divide the number of ticks by the amount of CPU cores. TODO(Subv): This yields only a
// rough approximation of the amount of executed ticks in the system, it may be thrown off
// if not all cores are doing a similar amount of work. Instead of doing this, we should
@@ -106,12 +109,8 @@ public:
}
u64 GetTicksRemaining() override {
- if (parent.uses_wall_clock) {
- if (!parent.interrupt_handlers[parent.core_index].IsInterrupted()) {
- return minimum_run_cycles;
- }
- return 0U;
- }
+ ASSERT_MSG(!parent.uses_wall_clock, "This should never happen - dynarmic ticking disabled");
+
return std::max<s64>(parent.system.CoreTiming().GetDowncount(), 0);
}
@@ -146,10 +145,18 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable*
// Timing
config.wall_clock_cntpct = uses_wall_clock;
+ config.enable_cycle_counting = !uses_wall_clock;
// Code cache size
- config.code_cache_size = 128_MiB;
- config.far_code_offset = 100_MiB;
+ config.code_cache_size = 512_MiB;
+ config.far_code_offset = 400_MiB;
+
+ // null_jit
+ if (!page_table) {
+ // Don't waste too much memory on null_jit
+ config.code_cache_size = 8_MiB;
+ config.far_code_offset = 4_MiB;
+ }
// Safe optimizations
if (Settings::values.cpu_debug_mode) {
@@ -186,35 +193,41 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable*
if (!Settings::values.cpuopt_recompile_exclusives) {
config.recompile_on_exclusive_fastmem_failure = false;
}
- }
+ } else {
+ // Unsafe optimizations
+ if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::Unsafe) {
+ config.unsafe_optimizations = true;
+ if (Settings::values.cpuopt_unsafe_unfuse_fma) {
+ config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA;
+ }
+ if (Settings::values.cpuopt_unsafe_reduce_fp_error) {
+ config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_ReducedErrorFP;
+ }
+ if (Settings::values.cpuopt_unsafe_ignore_standard_fpcr) {
+ config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_IgnoreStandardFPCRValue;
+ }
+ if (Settings::values.cpuopt_unsafe_inaccurate_nan) {
+ config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_InaccurateNaN;
+ }
+ if (Settings::values.cpuopt_unsafe_ignore_global_monitor) {
+ config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_IgnoreGlobalMonitor;
+ }
+ }
- // Unsafe optimizations
- if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::Unsafe) {
- config.unsafe_optimizations = true;
- if (Settings::values.cpuopt_unsafe_unfuse_fma) {
+ // Curated optimizations
+ if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::Auto) {
+ config.unsafe_optimizations = true;
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA;
- }
- if (Settings::values.cpuopt_unsafe_reduce_fp_error) {
- config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_ReducedErrorFP;
- }
- if (Settings::values.cpuopt_unsafe_ignore_standard_fpcr) {
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_IgnoreStandardFPCRValue;
- }
- if (Settings::values.cpuopt_unsafe_inaccurate_nan) {
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_InaccurateNaN;
- }
- if (Settings::values.cpuopt_unsafe_ignore_global_monitor) {
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_IgnoreGlobalMonitor;
}
- }
- // Curated optimizations
- if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::Auto) {
- config.unsafe_optimizations = true;
- config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA;
- config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_IgnoreStandardFPCRValue;
- config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_InaccurateNaN;
- config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_IgnoreGlobalMonitor;
+ // Paranoia mode for debugging optimizations
+ if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::Paranoid) {
+ config.unsafe_optimizations = false;
+ config.optimizations = Dynarmic::no_optimizations;
+ }
}
return std::make_unique<Dynarmic::A32::Jit>(config);
@@ -222,20 +235,18 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable*
void ARM_Dynarmic_32::Run() {
while (true) {
- jit->Run();
- if (!svc_called) {
- break;
+ const auto hr = jit.load()->Run();
+ if (Has(hr, svc_call)) {
+ Kernel::Svc::Call(system, svc_swi);
}
- svc_called = false;
- Kernel::Svc::Call(system, svc_swi);
- if (shutdown) {
+ if (Has(hr, break_loop) || !uses_wall_clock) {
break;
}
}
}
void ARM_Dynarmic_32::Step() {
- jit->Step();
+ jit.load()->Step();
}
ARM_Dynarmic_32::ARM_Dynarmic_32(System& system_, CPUInterrupts& interrupt_handlers_,
@@ -245,24 +256,28 @@ ARM_Dynarmic_32::ARM_Dynarmic_32(System& system_, CPUInterrupts& interrupt_handl
cb(std::make_unique<DynarmicCallbacks32>(*this)),
cp15(std::make_shared<DynarmicCP15>(*this)), core_index{core_index_},
exclusive_monitor{dynamic_cast<DynarmicExclusiveMonitor&>(exclusive_monitor_)},
- jit(MakeJit(nullptr)) {}
+ null_jit{MakeJit(nullptr)}, jit{null_jit.get()} {}
ARM_Dynarmic_32::~ARM_Dynarmic_32() = default;
void ARM_Dynarmic_32::SetPC(u64 pc) {
- jit->Regs()[15] = static_cast<u32>(pc);
+ jit.load()->Regs()[15] = static_cast<u32>(pc);
}
u64 ARM_Dynarmic_32::GetPC() const {
- return jit->Regs()[15];
+ return jit.load()->Regs()[15];
+}
+
+u64 ARM_Dynarmic_32::GetSP() const {
+ return jit.load()->Regs()[13];
}
u64 ARM_Dynarmic_32::GetReg(int index) const {
- return jit->Regs()[index];
+ return jit.load()->Regs()[index];
}
void ARM_Dynarmic_32::SetReg(int index, u64 value) {
- jit->Regs()[index] = static_cast<u32>(value);
+ jit.load()->Regs()[index] = static_cast<u32>(value);
}
u128 ARM_Dynarmic_32::GetVectorReg(int index) const {
@@ -272,11 +287,11 @@ u128 ARM_Dynarmic_32::GetVectorReg(int index) const {
void ARM_Dynarmic_32::SetVectorReg(int index, u128 value) {}
u32 ARM_Dynarmic_32::GetPSTATE() const {
- return jit->Cpsr();
+ return jit.load()->Cpsr();
}
void ARM_Dynarmic_32::SetPSTATE(u32 cpsr) {
- jit->SetCpsr(cpsr);
+ jit.load()->SetCpsr(cpsr);
}
u64 ARM_Dynarmic_32::GetTlsAddress() const {
@@ -297,7 +312,7 @@ void ARM_Dynarmic_32::SetTPIDR_EL0(u64 value) {
void ARM_Dynarmic_32::SaveContext(ThreadContext32& ctx) {
Dynarmic::A32::Context context;
- jit->SaveContext(context);
+ jit.load()->SaveContext(context);
ctx.cpu_registers = context.Regs();
ctx.extension_registers = context.ExtRegs();
ctx.cpsr = context.Cpsr();
@@ -310,24 +325,27 @@ void ARM_Dynarmic_32::LoadContext(const ThreadContext32& ctx) {
context.ExtRegs() = ctx.extension_registers;
context.SetCpsr(ctx.cpsr);
context.SetFpscr(ctx.fpscr);
- jit->LoadContext(context);
+ jit.load()->LoadContext(context);
}
void ARM_Dynarmic_32::PrepareReschedule() {
- jit->HaltExecution();
- shutdown = true;
+ jit.load()->HaltExecution(break_loop);
+}
+
+void ARM_Dynarmic_32::SignalInterrupt() {
+ jit.load()->HaltExecution(break_loop);
}
void ARM_Dynarmic_32::ClearInstructionCache() {
- jit->ClearCache();
+ jit.load()->ClearCache();
}
void ARM_Dynarmic_32::InvalidateCacheRange(VAddr addr, std::size_t size) {
- jit->InvalidateCacheRange(static_cast<u32>(addr), size);
+ jit.load()->InvalidateCacheRange(static_cast<u32>(addr), size);
}
void ARM_Dynarmic_32::ClearExclusiveState() {
- jit->ClearExclusiveState();
+ jit.load()->ClearExclusiveState();
}
void ARM_Dynarmic_32::PageTableChanged(Common::PageTable& page_table,
@@ -338,13 +356,29 @@ void ARM_Dynarmic_32::PageTableChanged(Common::PageTable& page_table,
auto key = std::make_pair(&page_table, new_address_space_size_in_bits);
auto iter = jit_cache.find(key);
if (iter != jit_cache.end()) {
- jit = iter->second;
+ jit.store(iter->second.get());
LoadContext(ctx);
return;
}
- jit = MakeJit(&page_table);
+ std::shared_ptr new_jit = MakeJit(&page_table);
+ jit.store(new_jit.get());
LoadContext(ctx);
- jit_cache.emplace(key, jit);
+ jit_cache.emplace(key, std::move(new_jit));
+}
+
+std::vector<ARM_Interface::BacktraceEntry> ARM_Dynarmic_32::GetBacktrace(Core::System& system,
+ u64 sp, u64 lr) {
+ // No way to get accurate stack traces in A32 yet
+ return {};
+}
+
+std::vector<ARM_Interface::BacktraceEntry> ARM_Dynarmic_32::GetBacktraceFromContext(
+ System& system, const ThreadContext32& ctx) {
+ return GetBacktrace(system, ctx.cpu_registers[13], ctx.cpu_registers[14]);
+}
+
+std::vector<ARM_Interface::BacktraceEntry> ARM_Dynarmic_32::GetBacktrace() const {
+ return GetBacktrace(system, GetReg(13), GetReg(14));
}
} // namespace Core
diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.h b/src/core/arm/dynarmic/arm_dynarmic_32.h
index 5d47b600d..1b628f94d 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_32.h
+++ b/src/core/arm/dynarmic/arm_dynarmic_32.h
@@ -4,6 +4,7 @@
#pragma once
+#include <atomic>
#include <memory>
#include <unordered_map>
@@ -34,6 +35,7 @@ public:
void SetPC(u64 pc) override;
u64 GetPC() const override;
+ u64 GetSP() const override;
u64 GetReg(int index) const override;
void SetReg(int index, u64 value) override;
u128 GetVectorReg(int index) const override;
@@ -57,6 +59,7 @@ public:
void LoadContext(const ThreadContext64& ctx) override {}
void PrepareReschedule() override;
+ void SignalInterrupt() override;
void ClearExclusiveState() override;
void ClearInstructionCache() override;
@@ -64,9 +67,16 @@ public:
void PageTableChanged(Common::PageTable& new_page_table,
std::size_t new_address_space_size_in_bits) override;
+ static std::vector<BacktraceEntry> GetBacktraceFromContext(System& system,
+ const ThreadContext32& ctx);
+
+ std::vector<BacktraceEntry> GetBacktrace() const override;
+
private:
std::shared_ptr<Dynarmic::A32::Jit> MakeJit(Common::PageTable* page_table) const;
+ static std::vector<BacktraceEntry> GetBacktrace(Core::System& system, u64 sp, u64 lr);
+
using JitCacheKey = std::pair<Common::PageTable*, std::size_t>;
using JitCacheType =
std::unordered_map<JitCacheKey, std::shared_ptr<Dynarmic::A32::Jit>, Common::PairHash>;
@@ -79,13 +89,14 @@ private:
std::shared_ptr<DynarmicCP15> cp15;
std::size_t core_index;
DynarmicExclusiveMonitor& exclusive_monitor;
- std::shared_ptr<Dynarmic::A32::Jit> jit;
+
+ std::shared_ptr<Dynarmic::A32::Jit> null_jit;
+
+ // A raw pointer here is fine; we never delete Jit instances.
+ std::atomic<Dynarmic::A32::Jit*> jit;
// SVC callback
u32 svc_swi{};
- bool svc_called{};
-
- bool shutdown{};
};
} // namespace Core
diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.cpp b/src/core/arm/dynarmic/arm_dynarmic_64.cpp
index aa74fce4d..871d9d10e 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_64.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic_64.cpp
@@ -26,6 +26,9 @@ namespace Core {
using Vector = Dynarmic::A64::Vector;
using namespace Common::Literals;
+constexpr Dynarmic::HaltReason break_loop = Dynarmic::HaltReason::UserDefined2;
+constexpr Dynarmic::HaltReason svc_call = Dynarmic::HaltReason::UserDefined3;
+
class DynarmicCallbacks64 : public Dynarmic::A64::UserCallbacks {
public:
explicit DynarmicCallbacks64(ARM_Dynarmic_64& parent_)
@@ -81,6 +84,7 @@ public:
}
void InterpreterFallback(u64 pc, std::size_t num_instructions) override {
+ parent.LogBacktrace();
LOG_ERROR(Core_ARM,
"Unimplemented instruction @ 0x{:X} for {} instructions (instr = {:08X})", pc,
num_instructions, MemoryReadCode(pc));
@@ -93,17 +97,19 @@ public:
static constexpr u64 ICACHE_LINE_SIZE = 64;
const u64 cache_line_start = value & ~(ICACHE_LINE_SIZE - 1);
- parent.InvalidateCacheRange(cache_line_start, ICACHE_LINE_SIZE);
+ parent.system.InvalidateCpuInstructionCacheRange(cache_line_start, ICACHE_LINE_SIZE);
break;
}
case Dynarmic::A64::InstructionCacheOperation::InvalidateAllToPoU:
- parent.ClearInstructionCache();
+ parent.system.InvalidateCpuInstructionCaches();
break;
case Dynarmic::A64::InstructionCacheOperation::InvalidateAllToPoUInnerSharable:
default:
LOG_DEBUG(Core_ARM, "Unprocesseed instruction cache operation: {}", op);
break;
}
+
+ parent.jit.load()->HaltExecution(Dynarmic::HaltReason::CacheInvalidation);
}
void ExceptionRaised(u64 pc, Dynarmic::A64::Exception exception) override {
@@ -116,21 +122,19 @@ public:
return;
case Dynarmic::A64::Exception::Breakpoint:
default:
+ parent.LogBacktrace();
ASSERT_MSG(false, "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X})",
static_cast<std::size_t>(exception), pc, MemoryReadCode(pc));
}
}
void CallSVC(u32 swi) override {
- parent.svc_called = true;
parent.svc_swi = swi;
- parent.jit->HaltExecution();
+ parent.jit.load()->HaltExecution(svc_call);
}
void AddTicks(u64 ticks) override {
- if (parent.uses_wall_clock) {
- return;
- }
+ ASSERT_MSG(!parent.uses_wall_clock, "This should never happen - dynarmic ticking disabled");
// Divide the number of ticks by the amount of CPU cores. TODO(Subv): This yields only a
// rough approximation of the amount of executed ticks in the system, it may be thrown off
@@ -145,12 +149,8 @@ public:
}
u64 GetTicksRemaining() override {
- if (parent.uses_wall_clock) {
- if (!parent.interrupt_handlers[parent.core_index].IsInterrupted()) {
- return minimum_run_cycles;
- }
- return 0U;
- }
+ ASSERT_MSG(!parent.uses_wall_clock, "This should never happen - dynarmic ticking disabled");
+
return std::max<s64>(parent.system.CoreTiming().GetDowncount(), 0);
}
@@ -206,10 +206,18 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable*
// Timing
config.wall_clock_cntpct = uses_wall_clock;
+ config.enable_cycle_counting = !uses_wall_clock;
// Code cache size
- config.code_cache_size = 128_MiB;
- config.far_code_offset = 100_MiB;
+ config.code_cache_size = 512_MiB;
+ config.far_code_offset = 400_MiB;
+
+ // null_jit
+ if (!page_table) {
+ // Don't waste too much memory on null_jit
+ config.code_cache_size = 8_MiB;
+ config.far_code_offset = 4_MiB;
+ }
// Safe optimizations
if (Settings::values.cpu_debug_mode) {
@@ -246,35 +254,41 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable*
if (!Settings::values.cpuopt_recompile_exclusives) {
config.recompile_on_exclusive_fastmem_failure = false;
}
- }
+ } else {
+ // Unsafe optimizations
+ if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::Unsafe) {
+ config.unsafe_optimizations = true;
+ if (Settings::values.cpuopt_unsafe_unfuse_fma) {
+ config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA;
+ }
+ if (Settings::values.cpuopt_unsafe_reduce_fp_error) {
+ config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_ReducedErrorFP;
+ }
+ if (Settings::values.cpuopt_unsafe_inaccurate_nan) {
+ config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_InaccurateNaN;
+ }
+ if (Settings::values.cpuopt_unsafe_fastmem_check) {
+ config.fastmem_address_space_bits = 64;
+ }
+ if (Settings::values.cpuopt_unsafe_ignore_global_monitor) {
+ config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_IgnoreGlobalMonitor;
+ }
+ }
- // Unsafe optimizations
- if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::Unsafe) {
- config.unsafe_optimizations = true;
- if (Settings::values.cpuopt_unsafe_unfuse_fma) {
+ // Curated optimizations
+ if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::Auto) {
+ config.unsafe_optimizations = true;
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA;
- }
- if (Settings::values.cpuopt_unsafe_reduce_fp_error) {
- config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_ReducedErrorFP;
- }
- if (Settings::values.cpuopt_unsafe_inaccurate_nan) {
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_InaccurateNaN;
- }
- if (Settings::values.cpuopt_unsafe_fastmem_check) {
config.fastmem_address_space_bits = 64;
- }
- if (Settings::values.cpuopt_unsafe_ignore_global_monitor) {
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_IgnoreGlobalMonitor;
}
- }
- // Curated optimizations
- if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::Auto) {
- config.unsafe_optimizations = true;
- config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA;
- config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_InaccurateNaN;
- config.fastmem_address_space_bits = 64;
- config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_IgnoreGlobalMonitor;
+ // Paranoia mode for debugging optimizations
+ if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::Paranoid) {
+ config.unsafe_optimizations = false;
+ config.optimizations = Dynarmic::no_optimizations;
+ }
}
return std::make_shared<Dynarmic::A64::Jit>(config);
@@ -282,20 +296,18 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable*
void ARM_Dynarmic_64::Run() {
while (true) {
- jit->Run();
- if (!svc_called) {
- break;
+ const auto hr = jit.load()->Run();
+ if (Has(hr, svc_call)) {
+ Kernel::Svc::Call(system, svc_swi);
}
- svc_called = false;
- Kernel::Svc::Call(system, svc_swi);
- if (shutdown) {
+ if (Has(hr, break_loop) || !uses_wall_clock) {
break;
}
}
}
void ARM_Dynarmic_64::Step() {
- jit->Step();
+ jit.load()->Step();
}
ARM_Dynarmic_64::ARM_Dynarmic_64(System& system_, CPUInterrupts& interrupt_handlers_,
@@ -304,40 +316,44 @@ ARM_Dynarmic_64::ARM_Dynarmic_64(System& system_, CPUInterrupts& interrupt_handl
: ARM_Interface{system_, interrupt_handlers_, uses_wall_clock_},
cb(std::make_unique<DynarmicCallbacks64>(*this)), core_index{core_index_},
exclusive_monitor{dynamic_cast<DynarmicExclusiveMonitor&>(exclusive_monitor_)},
- jit(MakeJit(nullptr, 48)) {}
+ null_jit{MakeJit(nullptr, 48)}, jit{null_jit.get()} {}
ARM_Dynarmic_64::~ARM_Dynarmic_64() = default;
void ARM_Dynarmic_64::SetPC(u64 pc) {
- jit->SetPC(pc);
+ jit.load()->SetPC(pc);
}
u64 ARM_Dynarmic_64::GetPC() const {
- return jit->GetPC();
+ return jit.load()->GetPC();
+}
+
+u64 ARM_Dynarmic_64::GetSP() const {
+ return jit.load()->GetSP();
}
u64 ARM_Dynarmic_64::GetReg(int index) const {
- return jit->GetRegister(index);
+ return jit.load()->GetRegister(index);
}
void ARM_Dynarmic_64::SetReg(int index, u64 value) {
- jit->SetRegister(index, value);
+ jit.load()->SetRegister(index, value);
}
u128 ARM_Dynarmic_64::GetVectorReg(int index) const {
- return jit->GetVector(index);
+ return jit.load()->GetVector(index);
}
void ARM_Dynarmic_64::SetVectorReg(int index, u128 value) {
- jit->SetVector(index, value);
+ jit.load()->SetVector(index, value);
}
u32 ARM_Dynarmic_64::GetPSTATE() const {
- return jit->GetPstate();
+ return jit.load()->GetPstate();
}
void ARM_Dynarmic_64::SetPSTATE(u32 pstate) {
- jit->SetPstate(pstate);
+ jit.load()->SetPstate(pstate);
}
u64 ARM_Dynarmic_64::GetTlsAddress() const {
@@ -357,42 +373,47 @@ void ARM_Dynarmic_64::SetTPIDR_EL0(u64 value) {
}
void ARM_Dynarmic_64::SaveContext(ThreadContext64& ctx) {
- ctx.cpu_registers = jit->GetRegisters();
- ctx.sp = jit->GetSP();
- ctx.pc = jit->GetPC();
- ctx.pstate = jit->GetPstate();
- ctx.vector_registers = jit->GetVectors();
- ctx.fpcr = jit->GetFpcr();
- ctx.fpsr = jit->GetFpsr();
+ Dynarmic::A64::Jit* j = jit.load();
+ ctx.cpu_registers = j->GetRegisters();
+ ctx.sp = j->GetSP();
+ ctx.pc = j->GetPC();
+ ctx.pstate = j->GetPstate();
+ ctx.vector_registers = j->GetVectors();
+ ctx.fpcr = j->GetFpcr();
+ ctx.fpsr = j->GetFpsr();
ctx.tpidr = cb->tpidr_el0;
}
void ARM_Dynarmic_64::LoadContext(const ThreadContext64& ctx) {
- jit->SetRegisters(ctx.cpu_registers);
- jit->SetSP(ctx.sp);
- jit->SetPC(ctx.pc);
- jit->SetPstate(ctx.pstate);
- jit->SetVectors(ctx.vector_registers);
- jit->SetFpcr(ctx.fpcr);
- jit->SetFpsr(ctx.fpsr);
+ Dynarmic::A64::Jit* j = jit.load();
+ j->SetRegisters(ctx.cpu_registers);
+ j->SetSP(ctx.sp);
+ j->SetPC(ctx.pc);
+ j->SetPstate(ctx.pstate);
+ j->SetVectors(ctx.vector_registers);
+ j->SetFpcr(ctx.fpcr);
+ j->SetFpsr(ctx.fpsr);
SetTPIDR_EL0(ctx.tpidr);
}
void ARM_Dynarmic_64::PrepareReschedule() {
- jit->HaltExecution();
- shutdown = true;
+ jit.load()->HaltExecution(break_loop);
+}
+
+void ARM_Dynarmic_64::SignalInterrupt() {
+ jit.load()->HaltExecution(break_loop);
}
void ARM_Dynarmic_64::ClearInstructionCache() {
- jit->ClearCache();
+ jit.load()->ClearCache();
}
void ARM_Dynarmic_64::InvalidateCacheRange(VAddr addr, std::size_t size) {
- jit->InvalidateCacheRange(addr, size);
+ jit.load()->InvalidateCacheRange(addr, size);
}
void ARM_Dynarmic_64::ClearExclusiveState() {
- jit->ClearExclusiveState();
+ jit.load()->ClearExclusiveState();
}
void ARM_Dynarmic_64::PageTableChanged(Common::PageTable& page_table,
@@ -403,13 +424,48 @@ void ARM_Dynarmic_64::PageTableChanged(Common::PageTable& page_table,
auto key = std::make_pair(&page_table, new_address_space_size_in_bits);
auto iter = jit_cache.find(key);
if (iter != jit_cache.end()) {
- jit = iter->second;
+ jit.store(iter->second.get());
LoadContext(ctx);
return;
}
- jit = MakeJit(&page_table, new_address_space_size_in_bits);
+ std::shared_ptr new_jit = MakeJit(&page_table, new_address_space_size_in_bits);
+ jit.store(new_jit.get());
LoadContext(ctx);
- jit_cache.emplace(key, jit);
+ jit_cache.emplace(key, std::move(new_jit));
+}
+
+std::vector<ARM_Interface::BacktraceEntry> ARM_Dynarmic_64::GetBacktrace(Core::System& system,
+ u64 fp, u64 lr) {
+ std::vector<BacktraceEntry> out;
+ auto& memory = system.Memory();
+
+ // fp (= r29) points to the last frame record.
+ // Note that this is the frame record for the *previous* frame, not the current one.
+ // Note we need to subtract 4 from our last read to get the proper address
+ // Frame records are two words long:
+ // fp+0 : pointer to previous frame record
+ // fp+8 : value of lr for frame
+ while (true) {
+ out.push_back({"", 0, lr, 0, ""});
+ if (!fp) {
+ break;
+ }
+ lr = memory.Read64(fp + 8) - 4;
+ fp = memory.Read64(fp);
+ }
+
+ SymbolicateBacktrace(system, out);
+
+ return out;
+}
+
+std::vector<ARM_Interface::BacktraceEntry> ARM_Dynarmic_64::GetBacktraceFromContext(
+ System& system, const ThreadContext64& ctx) {
+ return GetBacktrace(system, ctx.cpu_registers[29], ctx.cpu_registers[30]);
+}
+
+std::vector<ARM_Interface::BacktraceEntry> ARM_Dynarmic_64::GetBacktrace() const {
+ return GetBacktrace(system, GetReg(29), GetReg(30));
}
} // namespace Core
diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.h b/src/core/arm/dynarmic/arm_dynarmic_64.h
index 0c4e46c64..78773e293 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_64.h
+++ b/src/core/arm/dynarmic/arm_dynarmic_64.h
@@ -4,6 +4,7 @@
#pragma once
+#include <atomic>
#include <memory>
#include <unordered_map>
@@ -32,6 +33,7 @@ public:
void SetPC(u64 pc) override;
u64 GetPC() const override;
+ u64 GetSP() const override;
u64 GetReg(int index) const override;
void SetReg(int index, u64 value) override;
u128 GetVectorReg(int index) const override;
@@ -51,6 +53,7 @@ public:
void LoadContext(const ThreadContext64& ctx) override;
void PrepareReschedule() override;
+ void SignalInterrupt() override;
void ClearExclusiveState() override;
void ClearInstructionCache() override;
@@ -58,10 +61,17 @@ public:
void PageTableChanged(Common::PageTable& new_page_table,
std::size_t new_address_space_size_in_bits) override;
+ static std::vector<BacktraceEntry> GetBacktraceFromContext(System& system,
+ const ThreadContext64& ctx);
+
+ std::vector<BacktraceEntry> GetBacktrace() const override;
+
private:
std::shared_ptr<Dynarmic::A64::Jit> MakeJit(Common::PageTable* page_table,
std::size_t address_space_bits) const;
+ static std::vector<BacktraceEntry> GetBacktrace(Core::System& system, u64 fp, u64 lr);
+
using JitCacheKey = std::pair<Common::PageTable*, std::size_t>;
using JitCacheType =
std::unordered_map<JitCacheKey, std::shared_ptr<Dynarmic::A64::Jit>, Common::PairHash>;
@@ -73,13 +83,13 @@ private:
std::size_t core_index;
DynarmicExclusiveMonitor& exclusive_monitor;
- std::shared_ptr<Dynarmic::A64::Jit> jit;
+ std::shared_ptr<Dynarmic::A64::Jit> null_jit;
+
+ // A raw pointer here is fine; we never delete Jit instances.
+ std::atomic<Dynarmic::A64::Jit*> jit;
// SVC callback
u32 svc_swi{};
- bool svc_called{};
-
- bool shutdown{};
};
} // namespace Core
diff --git a/src/core/arm/symbols.cpp b/src/core/arm/symbols.cpp
new file mode 100644
index 000000000..26c44f0c7
--- /dev/null
+++ b/src/core/arm/symbols.cpp
@@ -0,0 +1,190 @@
+// Copyright 2022 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/bit_field.h"
+#include "common/common_funcs.h"
+#include "core/arm/symbols.h"
+#include "core/core.h"
+#include "core/memory.h"
+
+namespace Core {
+namespace {
+
+constexpr u64 ELF_DYNAMIC_TAG_NULL = 0;
+constexpr u64 ELF_DYNAMIC_TAG_STRTAB = 5;
+constexpr u64 ELF_DYNAMIC_TAG_SYMTAB = 6;
+constexpr u64 ELF_DYNAMIC_TAG_SYMENT = 11;
+
+enum class ELFSymbolType : u8 {
+ None = 0,
+ Object = 1,
+ Function = 2,
+ Section = 3,
+ File = 4,
+ Common = 5,
+ TLS = 6,
+};
+
+enum class ELFSymbolBinding : u8 {
+ Local = 0,
+ Global = 1,
+ Weak = 2,
+};
+
+enum class ELFSymbolVisibility : u8 {
+ Default = 0,
+ Internal = 1,
+ Hidden = 2,
+ Protected = 3,
+};
+
+struct ELF64Symbol {
+ u32 name_index;
+ union {
+ u8 info;
+
+ BitField<0, 4, ELFSymbolType> type;
+ BitField<4, 4, ELFSymbolBinding> binding;
+ };
+ ELFSymbolVisibility visibility;
+ u16 sh_index;
+ u64 value;
+ u64 size;
+};
+static_assert(sizeof(ELF64Symbol) == 0x18, "ELF64Symbol has incorrect size.");
+
+struct ELF32Symbol {
+ u32 name_index;
+ u32 value;
+ u32 size;
+ union {
+ u8 info;
+
+ BitField<0, 4, ELFSymbolType> type;
+ BitField<4, 4, ELFSymbolBinding> binding;
+ };
+ ELFSymbolVisibility visibility;
+ u16 sh_index;
+};
+static_assert(sizeof(ELF32Symbol) == 0x10, "ELF32Symbol has incorrect size.");
+
+} // Anonymous namespace
+
+namespace Symbols {
+
+template <typename Word, typename ELFSymbol, typename ByteReader>
+static Symbols GetSymbols(ByteReader ReadBytes) {
+ const auto Read8{[&](u64 index) {
+ u8 ret;
+ ReadBytes(&ret, index, sizeof(u8));
+ return ret;
+ }};
+
+ const auto Read32{[&](u64 index) {
+ u32 ret;
+ ReadBytes(&ret, index, sizeof(u32));
+ return ret;
+ }};
+
+ const auto ReadWord{[&](u64 index) {
+ Word ret;
+ ReadBytes(&ret, index, sizeof(Word));
+ return ret;
+ }};
+
+ const u32 mod_offset = Read32(4);
+
+ if (Read32(mod_offset) != Common::MakeMagic('M', 'O', 'D', '0')) {
+ return {};
+ }
+
+ VAddr string_table_offset{};
+ VAddr symbol_table_offset{};
+ u64 symbol_entry_size{};
+
+ const auto dynamic_offset = Read32(mod_offset + 0x4) + mod_offset;
+
+ VAddr dynamic_index = dynamic_offset;
+ while (true) {
+ const Word tag = ReadWord(dynamic_index);
+ const Word value = ReadWord(dynamic_index + sizeof(Word));
+ dynamic_index += 2 * sizeof(Word);
+
+ if (tag == ELF_DYNAMIC_TAG_NULL) {
+ break;
+ }
+
+ if (tag == ELF_DYNAMIC_TAG_STRTAB) {
+ string_table_offset = value;
+ } else if (tag == ELF_DYNAMIC_TAG_SYMTAB) {
+ symbol_table_offset = value;
+ } else if (tag == ELF_DYNAMIC_TAG_SYMENT) {
+ symbol_entry_size = value;
+ }
+ }
+
+ if (string_table_offset == 0 || symbol_table_offset == 0 || symbol_entry_size == 0) {
+ return {};
+ }
+
+ Symbols out;
+
+ VAddr symbol_index = symbol_table_offset;
+ while (symbol_index < string_table_offset) {
+ ELFSymbol symbol{};
+ ReadBytes(&symbol, symbol_index, sizeof(ELFSymbol));
+
+ VAddr string_offset = string_table_offset + symbol.name_index;
+ std::string name;
+ for (u8 c = Read8(string_offset); c != 0; c = Read8(++string_offset)) {
+ name += static_cast<char>(c);
+ }
+
+ symbol_index += symbol_entry_size;
+ out[name] = std::make_pair(symbol.value, symbol.size);
+ }
+
+ return out;
+}
+
+Symbols GetSymbols(VAddr base, Core::Memory::Memory& memory, bool is_64) {
+ const auto ReadBytes{
+ [&](void* ptr, size_t offset, size_t size) { memory.ReadBlock(base + offset, ptr, size); }};
+
+ if (is_64) {
+ return GetSymbols<u64, ELF64Symbol>(ReadBytes);
+ } else {
+ return GetSymbols<u32, ELF32Symbol>(ReadBytes);
+ }
+}
+
+Symbols GetSymbols(std::span<const u8> data, bool is_64) {
+ const auto ReadBytes{[&](void* ptr, size_t offset, size_t size) {
+ std::memcpy(ptr, data.data() + offset, size);
+ }};
+
+ if (is_64) {
+ return GetSymbols<u64, ELF64Symbol>(ReadBytes);
+ } else {
+ return GetSymbols<u32, ELF32Symbol>(ReadBytes);
+ }
+}
+
+std::optional<std::string> GetSymbolName(const Symbols& symbols, VAddr addr) {
+ const auto iter = std::find_if(symbols.cbegin(), symbols.cend(), [addr](const auto& pair) {
+ const auto& [name, sym_info] = pair;
+ const auto& [start_address, size] = sym_info;
+ const auto end_address = start_address + size;
+ return addr >= start_address && addr < end_address;
+ });
+
+ if (iter == symbols.cend()) {
+ return std::nullopt;
+ }
+
+ return iter->first;
+}
+
+} // namespace Symbols
+} // namespace Core
diff --git a/src/core/arm/symbols.h b/src/core/arm/symbols.h
new file mode 100644
index 000000000..99e6a9f8e
--- /dev/null
+++ b/src/core/arm/symbols.h
@@ -0,0 +1,27 @@
+// Copyright 2022 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <map>
+#include <optional>
+#include <span>
+#include <string>
+#include <utility>
+
+#include "common/common_types.h"
+
+namespace Core::Memory {
+class Memory;
+} // namespace Core::Memory
+
+namespace Core::Symbols {
+
+using Symbols = std::map<std::string, std::pair<VAddr, std::size_t>, std::less<>>;
+
+Symbols GetSymbols(VAddr base, Core::Memory::Memory& memory, bool is_64 = true);
+Symbols GetSymbols(std::span<const u8> data, bool is_64 = true);
+std::optional<std::string> GetSymbolName(const Symbols& symbols, VAddr addr);
+
+} // namespace Core::Symbols
diff --git a/src/core/core_timing.h b/src/core/core_timing.h
index 888828fd0..28b63be43 100644
--- a/src/core/core_timing.h
+++ b/src/core/core_timing.h
@@ -8,13 +8,13 @@
#include <chrono>
#include <functional>
#include <memory>
+#include <mutex>
#include <optional>
#include <string>
#include <thread>
#include <vector>
#include "common/common_types.h"
-#include "common/spin_lock.h"
#include "common/thread.h"
#include "common/wall_clock.h"
@@ -149,8 +149,8 @@ private:
std::shared_ptr<EventType> ev_lost;
Common::Event event{};
Common::Event pause_event{};
- Common::SpinLock basic_lock{};
- Common::SpinLock advance_lock{};
+ std::mutex basic_lock;
+ std::mutex advance_lock;
std::unique_ptr<std::thread> timer_thread;
std::atomic<bool> paused{};
std::atomic<bool> paused_set{};
diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp
index c4e185757..73e724f3d 100644
--- a/src/core/file_sys/patch_manager.cpp
+++ b/src/core/file_sys/patch_manager.cpp
@@ -148,29 +148,33 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
// LayeredExeFS
const auto load_dir = fs_controller.GetModificationLoadRoot(title_id);
+ const auto sdmc_load_dir = fs_controller.GetSDMCModificationLoadRoot(title_id);
+
+ std::vector<VirtualDir> patch_dirs = {sdmc_load_dir};
if (load_dir != nullptr && load_dir->GetSize() > 0) {
- auto patch_dirs = load_dir->GetSubdirectories();
- std::sort(
- patch_dirs.begin(), patch_dirs.end(),
- [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
-
- std::vector<VirtualDir> layers;
- layers.reserve(patch_dirs.size() + 1);
- for (const auto& subdir : patch_dirs) {
- if (std::find(disabled.begin(), disabled.end(), subdir->GetName()) != disabled.end())
- continue;
+ const auto load_patch_dirs = load_dir->GetSubdirectories();
+ patch_dirs.insert(patch_dirs.end(), load_patch_dirs.begin(), load_patch_dirs.end());
+ }
- auto exefs_dir = FindSubdirectoryCaseless(subdir, "exefs");
- if (exefs_dir != nullptr)
- layers.push_back(std::move(exefs_dir));
- }
- layers.push_back(exefs);
+ std::sort(patch_dirs.begin(), patch_dirs.end(),
+ [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
- auto layered = LayeredVfsDirectory::MakeLayeredDirectory(std::move(layers));
- if (layered != nullptr) {
- LOG_INFO(Loader, " ExeFS: LayeredExeFS patches applied successfully");
- exefs = std::move(layered);
- }
+ std::vector<VirtualDir> layers;
+ layers.reserve(patch_dirs.size() + 1);
+ for (const auto& subdir : patch_dirs) {
+ if (std::find(disabled.begin(), disabled.end(), subdir->GetName()) != disabled.end())
+ continue;
+
+ auto exefs_dir = FindSubdirectoryCaseless(subdir, "exefs");
+ if (exefs_dir != nullptr)
+ layers.push_back(std::move(exefs_dir));
+ }
+ layers.push_back(exefs);
+
+ auto layered = LayeredVfsDirectory::MakeLayeredDirectory(std::move(layers));
+ if (layered != nullptr) {
+ LOG_INFO(Loader, " ExeFS: LayeredExeFS patches applied successfully");
+ exefs = std::move(layered);
}
if (Settings::values.dump_exefs) {
@@ -536,11 +540,20 @@ PatchManager::PatchVersionNames PatchManager::GetPatchVersionNames(VirtualFile u
// SDMC mod directory (RomFS LayeredFS)
const auto sdmc_mod_dir = fs_controller.GetSDMCModificationLoadRoot(title_id);
- if (sdmc_mod_dir != nullptr && sdmc_mod_dir->GetSize() > 0 &&
- IsDirValidAndNonEmpty(FindSubdirectoryCaseless(sdmc_mod_dir, "romfs"))) {
- const auto mod_disabled =
- std::find(disabled.begin(), disabled.end(), "SDMC") != disabled.end();
- out.insert_or_assign(mod_disabled ? "[D] SDMC" : "SDMC", "LayeredFS");
+ if (sdmc_mod_dir != nullptr && sdmc_mod_dir->GetSize() > 0) {
+ std::string types;
+ if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(sdmc_mod_dir, "exefs"))) {
+ AppendCommaIfNotEmpty(types, "LayeredExeFS");
+ }
+ if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(sdmc_mod_dir, "romfs"))) {
+ AppendCommaIfNotEmpty(types, "LayeredFS");
+ }
+
+ if (!types.empty()) {
+ const auto mod_disabled =
+ std::find(disabled.begin(), disabled.end(), "SDMC") != disabled.end();
+ out.insert_or_assign(mod_disabled ? "[D] SDMC" : "SDMC", types);
+ }
}
// DLC
diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp
index 7a646b5f1..4ada4a69b 100644
--- a/src/core/file_sys/registered_cache.cpp
+++ b/src/core/file_sys/registered_cache.cpp
@@ -387,15 +387,17 @@ std::vector<NcaID> RegisteredCache::AccumulateFiles() const {
continue;
for (const auto& nca_dir : d2_dir->GetSubdirectories()) {
- if (!FollowsNcaIdFormat(nca_dir->GetName()))
+ if (nca_dir == nullptr || !FollowsNcaIdFormat(nca_dir->GetName())) {
continue;
+ }
ids.push_back(Common::HexStringToArray<0x10, true>(nca_dir->GetName().substr(0, 0x20)));
}
for (const auto& nca_file : d2_dir->GetFiles()) {
- if (!FollowsNcaIdFormat(nca_file->GetName()))
+ if (nca_file == nullptr || !FollowsNcaIdFormat(nca_file->GetName())) {
continue;
+ }
ids.push_back(
Common::HexStringToArray<0x10, true>(nca_file->GetName().substr(0, 0x20)));
diff --git a/src/core/frontend/applets/mii.cpp b/src/core/frontend/applets/mii.cpp
deleted file mode 100644
index 1c05ff412..000000000
--- a/src/core/frontend/applets/mii.cpp
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2022 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include "common/logging/log.h"
-#include "core/frontend/applets/mii.h"
-
-namespace Core::Frontend {
-
-MiiApplet::~MiiApplet() = default;
-
-void DefaultMiiApplet::ShowMii(
- const MiiParameters& parameters,
- const std::function<void(const Core::Frontend::MiiParameters& parameters)> callback) const {
- LOG_INFO(Service_HID, "(STUBBED) called");
- callback(parameters);
-}
-
-} // namespace Core::Frontend
diff --git a/src/core/frontend/applets/mii.h b/src/core/frontend/applets/mii.h
deleted file mode 100644
index 11ba8a359..000000000
--- a/src/core/frontend/applets/mii.h
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright 2022 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <functional>
-
-#include "core/hle/service/mii/mii_manager.h"
-
-namespace Core::Frontend {
-
-struct MiiParameters {
- bool is_editable;
- Service::Mii::MiiInfo mii_data{};
-};
-
-class MiiApplet {
-public:
- virtual ~MiiApplet();
-
- virtual void ShowMii(const MiiParameters& parameters,
- const std::function<void(const Core::Frontend::MiiParameters& parameters)>
- callback) const = 0;
-};
-
-class DefaultMiiApplet final : public MiiApplet {
-public:
- void ShowMii(const MiiParameters& parameters,
- const std::function<void(const Core::Frontend::MiiParameters& parameters)>
- callback) const override;
-};
-
-} // namespace Core::Frontend
diff --git a/src/core/frontend/applets/mii_edit.cpp b/src/core/frontend/applets/mii_edit.cpp
new file mode 100644
index 000000000..fadb5fb15
--- /dev/null
+++ b/src/core/frontend/applets/mii_edit.cpp
@@ -0,0 +1,18 @@
+// Copyright 2022 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/logging/log.h"
+#include "core/frontend/applets/mii_edit.h"
+
+namespace Core::Frontend {
+
+MiiEditApplet::~MiiEditApplet() = default;
+
+void DefaultMiiEditApplet::ShowMiiEdit(const std::function<void()>& callback) const {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+
+ callback();
+}
+
+} // namespace Core::Frontend
diff --git a/src/core/frontend/applets/mii_edit.h b/src/core/frontend/applets/mii_edit.h
new file mode 100644
index 000000000..cca0e931d
--- /dev/null
+++ b/src/core/frontend/applets/mii_edit.h
@@ -0,0 +1,23 @@
+// Copyright 2022 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <functional>
+
+namespace Core::Frontend {
+
+class MiiEditApplet {
+public:
+ virtual ~MiiEditApplet();
+
+ virtual void ShowMiiEdit(const std::function<void()>& callback) const = 0;
+};
+
+class DefaultMiiEditApplet final : public MiiEditApplet {
+public:
+ void ShowMiiEdit(const std::function<void()>& callback) const override;
+};
+
+} // namespace Core::Frontend
diff --git a/src/core/hid/emulated_console.cpp b/src/core/hid/emulated_console.cpp
index eef0ff493..de565048b 100644
--- a/src/core/hid/emulated_console.cpp
+++ b/src/core/hid/emulated_console.cpp
@@ -132,7 +132,7 @@ void EmulatedConsole::SetMotionParam(Common::ParamPackage param) {
}
void EmulatedConsole::SetMotion(const Common::Input::CallbackStatus& callback) {
- std::lock_guard lock{mutex};
+ std::unique_lock lock{mutex};
auto& raw_status = console.motion_values.raw_status;
auto& emulated = console.motion_values.emulated;
@@ -151,6 +151,7 @@ void EmulatedConsole::SetMotion(const Common::Input::CallbackStatus& callback) {
emulated.UpdateOrientation(raw_status.delta_timestamp);
if (is_configuring) {
+ lock.unlock();
TriggerOnChange(ConsoleTriggerType::Motion);
return;
}
@@ -166,6 +167,7 @@ void EmulatedConsole::SetMotion(const Common::Input::CallbackStatus& callback) {
// Find what is this value
motion.verticalization_error = 0.0f;
+ lock.unlock();
TriggerOnChange(ConsoleTriggerType::Motion);
}
@@ -173,11 +175,12 @@ void EmulatedConsole::SetTouch(const Common::Input::CallbackStatus& callback, st
if (index >= console.touch_values.size()) {
return;
}
- std::lock_guard lock{mutex};
+ std::unique_lock lock{mutex};
console.touch_values[index] = TransformToTouch(callback);
if (is_configuring) {
+ lock.unlock();
TriggerOnChange(ConsoleTriggerType::Touch);
return;
}
@@ -189,26 +192,32 @@ void EmulatedConsole::SetTouch(const Common::Input::CallbackStatus& callback, st
.pressed = console.touch_values[index].pressed.value,
};
+ lock.unlock();
TriggerOnChange(ConsoleTriggerType::Touch);
}
ConsoleMotionValues EmulatedConsole::GetMotionValues() const {
+ std::scoped_lock lock{mutex};
return console.motion_values;
}
TouchValues EmulatedConsole::GetTouchValues() const {
+ std::scoped_lock lock{mutex};
return console.touch_values;
}
ConsoleMotion EmulatedConsole::GetMotion() const {
+ std::scoped_lock lock{mutex};
return console.motion_state;
}
TouchFingerState EmulatedConsole::GetTouch() const {
+ std::scoped_lock lock{mutex};
return console.touch_state;
}
void EmulatedConsole::TriggerOnChange(ConsoleTriggerType type) {
+ std::scoped_lock lock{callback_mutex};
for (const auto& poller_pair : callback_list) {
const ConsoleUpdateCallback& poller = poller_pair.second;
if (poller.on_change) {
@@ -218,13 +227,13 @@ void EmulatedConsole::TriggerOnChange(ConsoleTriggerType type) {
}
int EmulatedConsole::SetCallback(ConsoleUpdateCallback update_callback) {
- std::lock_guard lock{mutex};
+ std::scoped_lock lock{callback_mutex};
callback_list.insert_or_assign(last_callback_key, update_callback);
return last_callback_key++;
}
void EmulatedConsole::DeleteCallback(int key) {
- std::lock_guard lock{mutex};
+ std::scoped_lock lock{callback_mutex};
const auto& iterator = callback_list.find(key);
if (iterator == callback_list.end()) {
LOG_ERROR(Input, "Tried to delete non-existent callback {}", key);
diff --git a/src/core/hid/emulated_console.h b/src/core/hid/emulated_console.h
index 5eb170823..53677bdc5 100644
--- a/src/core/hid/emulated_console.h
+++ b/src/core/hid/emulated_console.h
@@ -183,6 +183,7 @@ private:
TouchDevices touch_devices;
mutable std::mutex mutex;
+ mutable std::mutex callback_mutex;
std::unordered_map<int, ConsoleUpdateCallback> callback_list;
int last_callback_key = 0;
diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp
index 7e05666d6..c3f21066c 100644
--- a/src/core/hid/emulated_controller.cpp
+++ b/src/core/hid/emulated_controller.cpp
@@ -353,14 +353,17 @@ void EmulatedController::DisableConfiguration() {
}
void EmulatedController::EnableSystemButtons() {
+ std::scoped_lock lock{mutex};
system_buttons_enabled = true;
}
void EmulatedController::DisableSystemButtons() {
+ std::scoped_lock lock{mutex};
system_buttons_enabled = false;
}
void EmulatedController::ResetSystemButtons() {
+ std::scoped_lock lock{mutex};
controller.home_button_state.home.Assign(false);
controller.capture_button_state.capture.Assign(false);
}
@@ -494,139 +497,141 @@ void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback
if (index >= controller.button_values.size()) {
return;
}
- {
- std::lock_guard lock{mutex};
- bool value_changed = false;
- const auto new_status = TransformToButton(callback);
- auto& current_status = controller.button_values[index];
+ std::unique_lock lock{mutex};
+ bool value_changed = false;
+ const auto new_status = TransformToButton(callback);
+ auto& current_status = controller.button_values[index];
- // Only read button values that have the same uuid or are pressed once
- if (current_status.uuid != uuid) {
- if (!new_status.value) {
- return;
- }
+ // Only read button values that have the same uuid or are pressed once
+ if (current_status.uuid != uuid) {
+ if (!new_status.value) {
+ return;
}
+ }
- current_status.toggle = new_status.toggle;
- current_status.uuid = uuid;
-
- // Update button status with current
- if (!current_status.toggle) {
- current_status.locked = false;
- if (current_status.value != new_status.value) {
- current_status.value = new_status.value;
- value_changed = true;
- }
- } else {
- // Toggle button and lock status
- if (new_status.value && !current_status.locked) {
- current_status.locked = true;
- current_status.value = !current_status.value;
- value_changed = true;
- }
+ current_status.toggle = new_status.toggle;
+ current_status.uuid = uuid;
- // Unlock button ready for next press
- if (!new_status.value && current_status.locked) {
- current_status.locked = false;
- }
+ // Update button status with current
+ if (!current_status.toggle) {
+ current_status.locked = false;
+ if (current_status.value != new_status.value) {
+ current_status.value = new_status.value;
+ value_changed = true;
}
-
- if (!value_changed) {
- return;
+ } else {
+ // Toggle button and lock status
+ if (new_status.value && !current_status.locked) {
+ current_status.locked = true;
+ current_status.value = !current_status.value;
+ value_changed = true;
}
- if (is_configuring) {
- controller.npad_button_state.raw = NpadButton::None;
- controller.debug_pad_button_state.raw = 0;
- TriggerOnChange(ControllerTriggerType::Button, false);
- return;
+ // Unlock button ready for next press
+ if (!new_status.value && current_status.locked) {
+ current_status.locked = false;
}
+ }
- switch (index) {
- case Settings::NativeButton::A:
- controller.npad_button_state.a.Assign(current_status.value);
- controller.debug_pad_button_state.a.Assign(current_status.value);
- break;
- case Settings::NativeButton::B:
- controller.npad_button_state.b.Assign(current_status.value);
- controller.debug_pad_button_state.b.Assign(current_status.value);
- break;
- case Settings::NativeButton::X:
- controller.npad_button_state.x.Assign(current_status.value);
- controller.debug_pad_button_state.x.Assign(current_status.value);
- break;
- case Settings::NativeButton::Y:
- controller.npad_button_state.y.Assign(current_status.value);
- controller.debug_pad_button_state.y.Assign(current_status.value);
- break;
- case Settings::NativeButton::LStick:
- controller.npad_button_state.stick_l.Assign(current_status.value);
- break;
- case Settings::NativeButton::RStick:
- controller.npad_button_state.stick_r.Assign(current_status.value);
- break;
- case Settings::NativeButton::L:
- controller.npad_button_state.l.Assign(current_status.value);
- controller.debug_pad_button_state.l.Assign(current_status.value);
- break;
- case Settings::NativeButton::R:
- controller.npad_button_state.r.Assign(current_status.value);
- controller.debug_pad_button_state.r.Assign(current_status.value);
- break;
- case Settings::NativeButton::ZL:
- controller.npad_button_state.zl.Assign(current_status.value);
- controller.debug_pad_button_state.zl.Assign(current_status.value);
- break;
- case Settings::NativeButton::ZR:
- controller.npad_button_state.zr.Assign(current_status.value);
- controller.debug_pad_button_state.zr.Assign(current_status.value);
- break;
- case Settings::NativeButton::Plus:
- controller.npad_button_state.plus.Assign(current_status.value);
- controller.debug_pad_button_state.plus.Assign(current_status.value);
- break;
- case Settings::NativeButton::Minus:
- controller.npad_button_state.minus.Assign(current_status.value);
- controller.debug_pad_button_state.minus.Assign(current_status.value);
- break;
- case Settings::NativeButton::DLeft:
- controller.npad_button_state.left.Assign(current_status.value);
- controller.debug_pad_button_state.d_left.Assign(current_status.value);
- break;
- case Settings::NativeButton::DUp:
- controller.npad_button_state.up.Assign(current_status.value);
- controller.debug_pad_button_state.d_up.Assign(current_status.value);
- break;
- case Settings::NativeButton::DRight:
- controller.npad_button_state.right.Assign(current_status.value);
- controller.debug_pad_button_state.d_right.Assign(current_status.value);
- break;
- case Settings::NativeButton::DDown:
- controller.npad_button_state.down.Assign(current_status.value);
- controller.debug_pad_button_state.d_down.Assign(current_status.value);
- break;
- case Settings::NativeButton::SL:
- controller.npad_button_state.left_sl.Assign(current_status.value);
- controller.npad_button_state.right_sl.Assign(current_status.value);
- break;
- case Settings::NativeButton::SR:
- controller.npad_button_state.left_sr.Assign(current_status.value);
- controller.npad_button_state.right_sr.Assign(current_status.value);
- break;
- case Settings::NativeButton::Home:
- if (!system_buttons_enabled) {
- break;
- }
- controller.home_button_state.home.Assign(current_status.value);
+ if (!value_changed) {
+ return;
+ }
+
+ if (is_configuring) {
+ controller.npad_button_state.raw = NpadButton::None;
+ controller.debug_pad_button_state.raw = 0;
+ lock.unlock();
+ TriggerOnChange(ControllerTriggerType::Button, false);
+ return;
+ }
+
+ switch (index) {
+ case Settings::NativeButton::A:
+ controller.npad_button_state.a.Assign(current_status.value);
+ controller.debug_pad_button_state.a.Assign(current_status.value);
+ break;
+ case Settings::NativeButton::B:
+ controller.npad_button_state.b.Assign(current_status.value);
+ controller.debug_pad_button_state.b.Assign(current_status.value);
+ break;
+ case Settings::NativeButton::X:
+ controller.npad_button_state.x.Assign(current_status.value);
+ controller.debug_pad_button_state.x.Assign(current_status.value);
+ break;
+ case Settings::NativeButton::Y:
+ controller.npad_button_state.y.Assign(current_status.value);
+ controller.debug_pad_button_state.y.Assign(current_status.value);
+ break;
+ case Settings::NativeButton::LStick:
+ controller.npad_button_state.stick_l.Assign(current_status.value);
+ break;
+ case Settings::NativeButton::RStick:
+ controller.npad_button_state.stick_r.Assign(current_status.value);
+ break;
+ case Settings::NativeButton::L:
+ controller.npad_button_state.l.Assign(current_status.value);
+ controller.debug_pad_button_state.l.Assign(current_status.value);
+ break;
+ case Settings::NativeButton::R:
+ controller.npad_button_state.r.Assign(current_status.value);
+ controller.debug_pad_button_state.r.Assign(current_status.value);
+ break;
+ case Settings::NativeButton::ZL:
+ controller.npad_button_state.zl.Assign(current_status.value);
+ controller.debug_pad_button_state.zl.Assign(current_status.value);
+ break;
+ case Settings::NativeButton::ZR:
+ controller.npad_button_state.zr.Assign(current_status.value);
+ controller.debug_pad_button_state.zr.Assign(current_status.value);
+ break;
+ case Settings::NativeButton::Plus:
+ controller.npad_button_state.plus.Assign(current_status.value);
+ controller.debug_pad_button_state.plus.Assign(current_status.value);
+ break;
+ case Settings::NativeButton::Minus:
+ controller.npad_button_state.minus.Assign(current_status.value);
+ controller.debug_pad_button_state.minus.Assign(current_status.value);
+ break;
+ case Settings::NativeButton::DLeft:
+ controller.npad_button_state.left.Assign(current_status.value);
+ controller.debug_pad_button_state.d_left.Assign(current_status.value);
+ break;
+ case Settings::NativeButton::DUp:
+ controller.npad_button_state.up.Assign(current_status.value);
+ controller.debug_pad_button_state.d_up.Assign(current_status.value);
+ break;
+ case Settings::NativeButton::DRight:
+ controller.npad_button_state.right.Assign(current_status.value);
+ controller.debug_pad_button_state.d_right.Assign(current_status.value);
+ break;
+ case Settings::NativeButton::DDown:
+ controller.npad_button_state.down.Assign(current_status.value);
+ controller.debug_pad_button_state.d_down.Assign(current_status.value);
+ break;
+ case Settings::NativeButton::SL:
+ controller.npad_button_state.left_sl.Assign(current_status.value);
+ controller.npad_button_state.right_sl.Assign(current_status.value);
+ break;
+ case Settings::NativeButton::SR:
+ controller.npad_button_state.left_sr.Assign(current_status.value);
+ controller.npad_button_state.right_sr.Assign(current_status.value);
+ break;
+ case Settings::NativeButton::Home:
+ if (!system_buttons_enabled) {
break;
- case Settings::NativeButton::Screenshot:
- if (!system_buttons_enabled) {
- break;
- }
- controller.capture_button_state.capture.Assign(current_status.value);
+ }
+ controller.home_button_state.home.Assign(current_status.value);
+ break;
+ case Settings::NativeButton::Screenshot:
+ if (!system_buttons_enabled) {
break;
}
+ controller.capture_button_state.capture.Assign(current_status.value);
+ break;
}
+
+ lock.unlock();
+
if (!is_connected) {
if (npad_id_type == NpadIdType::Player1 && npad_type != NpadStyleIndex::Handheld) {
Connect();
@@ -643,7 +648,7 @@ void EmulatedController::SetStick(const Common::Input::CallbackStatus& callback,
if (index >= controller.stick_values.size()) {
return;
}
- std::lock_guard lock{mutex};
+ std::unique_lock lock{mutex};
const auto stick_value = TransformToStick(callback);
// Only read stick values that have the same uuid or are over the threshold to avoid flapping
@@ -659,6 +664,7 @@ void EmulatedController::SetStick(const Common::Input::CallbackStatus& callback,
if (is_configuring) {
controller.analog_stick_state.left = {};
controller.analog_stick_state.right = {};
+ lock.unlock();
TriggerOnChange(ControllerTriggerType::Stick, false);
return;
}
@@ -685,6 +691,7 @@ void EmulatedController::SetStick(const Common::Input::CallbackStatus& callback,
break;
}
+ lock.unlock();
TriggerOnChange(ControllerTriggerType::Stick, true);
}
@@ -693,7 +700,7 @@ void EmulatedController::SetTrigger(const Common::Input::CallbackStatus& callbac
if (index >= controller.trigger_values.size()) {
return;
}
- std::lock_guard lock{mutex};
+ std::unique_lock lock{mutex};
const auto trigger_value = TransformToTrigger(callback);
// Only read trigger values that have the same uuid or are pressed once
@@ -709,6 +716,7 @@ void EmulatedController::SetTrigger(const Common::Input::CallbackStatus& callbac
if (is_configuring) {
controller.gc_trigger_state.left = 0;
controller.gc_trigger_state.right = 0;
+ lock.unlock();
TriggerOnChange(ControllerTriggerType::Trigger, false);
return;
}
@@ -727,6 +735,7 @@ void EmulatedController::SetTrigger(const Common::Input::CallbackStatus& callbac
break;
}
+ lock.unlock();
TriggerOnChange(ControllerTriggerType::Trigger, true);
}
@@ -735,7 +744,7 @@ void EmulatedController::SetMotion(const Common::Input::CallbackStatus& callback
if (index >= controller.motion_values.size()) {
return;
}
- std::lock_guard lock{mutex};
+ std::unique_lock lock{mutex};
auto& raw_status = controller.motion_values[index].raw_status;
auto& emulated = controller.motion_values[index].emulated;
@@ -756,6 +765,7 @@ void EmulatedController::SetMotion(const Common::Input::CallbackStatus& callback
force_update_motion = raw_status.force_update;
if (is_configuring) {
+ lock.unlock();
TriggerOnChange(ControllerTriggerType::Motion, false);
return;
}
@@ -767,6 +777,7 @@ void EmulatedController::SetMotion(const Common::Input::CallbackStatus& callback
motion.orientation = emulated.GetOrientation();
motion.is_at_rest = !emulated.IsMoving(motion_sensitivity);
+ lock.unlock();
TriggerOnChange(ControllerTriggerType::Motion, true);
}
@@ -775,10 +786,11 @@ void EmulatedController::SetBattery(const Common::Input::CallbackStatus& callbac
if (index >= controller.battery_values.size()) {
return;
}
- std::lock_guard lock{mutex};
+ std::unique_lock lock{mutex};
controller.battery_values[index] = TransformToBattery(callback);
if (is_configuring) {
+ lock.unlock();
TriggerOnChange(ControllerTriggerType::Battery, false);
return;
}
@@ -835,6 +847,8 @@ void EmulatedController::SetBattery(const Common::Input::CallbackStatus& callbac
};
break;
}
+
+ lock.unlock();
TriggerOnChange(ControllerTriggerType::Battery, true);
}
@@ -932,6 +946,7 @@ void EmulatedController::SetSupportedNpadStyleTag(NpadStyleTag supported_styles)
}
bool EmulatedController::IsControllerFullkey(bool use_temporary_value) const {
+ std::scoped_lock lock{mutex};
const auto type = is_configuring && use_temporary_value ? tmp_npad_type : npad_type;
switch (type) {
case NpadStyleIndex::ProController:
@@ -947,6 +962,7 @@ bool EmulatedController::IsControllerFullkey(bool use_temporary_value) const {
}
bool EmulatedController::IsControllerSupported(bool use_temporary_value) const {
+ std::scoped_lock lock{mutex};
const auto type = is_configuring && use_temporary_value ? tmp_npad_type : npad_type;
switch (type) {
case NpadStyleIndex::ProController:
@@ -982,40 +998,44 @@ void EmulatedController::Connect(bool use_temporary_value) {
LOG_ERROR(Service_HID, "Controller type {} is not supported", type);
return;
}
- {
- std::lock_guard lock{mutex};
- if (is_configuring) {
- tmp_is_connected = true;
- TriggerOnChange(ControllerTriggerType::Connected, false);
- return;
- }
- if (is_connected) {
- return;
- }
- is_connected = true;
+ std::unique_lock lock{mutex};
+ if (is_configuring) {
+ tmp_is_connected = true;
+ lock.unlock();
+ TriggerOnChange(ControllerTriggerType::Connected, false);
+ return;
+ }
+
+ if (is_connected) {
+ return;
}
+ is_connected = true;
+
+ lock.unlock();
TriggerOnChange(ControllerTriggerType::Connected, true);
}
void EmulatedController::Disconnect() {
- {
- std::lock_guard lock{mutex};
- if (is_configuring) {
- tmp_is_connected = false;
- TriggerOnChange(ControllerTriggerType::Disconnected, false);
- return;
- }
+ std::unique_lock lock{mutex};
+ if (is_configuring) {
+ tmp_is_connected = false;
+ lock.unlock();
+ TriggerOnChange(ControllerTriggerType::Disconnected, false);
+ return;
+ }
- if (!is_connected) {
- return;
- }
- is_connected = false;
+ if (!is_connected) {
+ return;
}
+ is_connected = false;
+
+ lock.unlock();
TriggerOnChange(ControllerTriggerType::Disconnected, true);
}
bool EmulatedController::IsConnected(bool get_temporary_value) const {
+ std::scoped_lock lock{mutex};
if (get_temporary_value && is_configuring) {
return tmp_is_connected;
}
@@ -1029,10 +1049,12 @@ bool EmulatedController::IsVibrationEnabled() const {
}
NpadIdType EmulatedController::GetNpadIdType() const {
+ std::scoped_lock lock{mutex};
return npad_id_type;
}
NpadStyleIndex EmulatedController::GetNpadStyleIndex(bool get_temporary_value) const {
+ std::scoped_lock lock{mutex};
if (get_temporary_value && is_configuring) {
return tmp_npad_type;
}
@@ -1040,27 +1062,28 @@ NpadStyleIndex EmulatedController::GetNpadStyleIndex(bool get_temporary_value) c
}
void EmulatedController::SetNpadStyleIndex(NpadStyleIndex npad_type_) {
- {
- std::lock_guard lock{mutex};
+ std::unique_lock lock{mutex};
- if (is_configuring) {
- if (tmp_npad_type == npad_type_) {
- return;
- }
- tmp_npad_type = npad_type_;
- TriggerOnChange(ControllerTriggerType::Type, false);
+ if (is_configuring) {
+ if (tmp_npad_type == npad_type_) {
return;
}
+ tmp_npad_type = npad_type_;
+ lock.unlock();
+ TriggerOnChange(ControllerTriggerType::Type, false);
+ return;
+ }
- if (npad_type == npad_type_) {
- return;
- }
- if (is_connected) {
- LOG_WARNING(Service_HID, "Controller {} type changed while it's connected",
- NpadIdTypeToIndex(npad_id_type));
- }
- npad_type = npad_type_;
+ if (npad_type == npad_type_) {
+ return;
+ }
+ if (is_connected) {
+ LOG_WARNING(Service_HID, "Controller {} type changed while it's connected",
+ NpadIdTypeToIndex(npad_id_type));
}
+ npad_type = npad_type_;
+
+ lock.unlock();
TriggerOnChange(ControllerTriggerType::Type, true);
}
@@ -1088,30 +1111,37 @@ LedPattern EmulatedController::GetLedPattern() const {
}
ButtonValues EmulatedController::GetButtonsValues() const {
+ std::scoped_lock lock{mutex};
return controller.button_values;
}
SticksValues EmulatedController::GetSticksValues() const {
+ std::scoped_lock lock{mutex};
return controller.stick_values;
}
TriggerValues EmulatedController::GetTriggersValues() const {
+ std::scoped_lock lock{mutex};
return controller.trigger_values;
}
ControllerMotionValues EmulatedController::GetMotionValues() const {
+ std::scoped_lock lock{mutex};
return controller.motion_values;
}
ColorValues EmulatedController::GetColorsValues() const {
+ std::scoped_lock lock{mutex};
return controller.color_values;
}
BatteryValues EmulatedController::GetBatteryValues() const {
+ std::scoped_lock lock{mutex};
return controller.battery_values;
}
HomeButtonState EmulatedController::GetHomeButtons() const {
+ std::scoped_lock lock{mutex};
if (is_configuring) {
return {};
}
@@ -1119,6 +1149,7 @@ HomeButtonState EmulatedController::GetHomeButtons() const {
}
CaptureButtonState EmulatedController::GetCaptureButtons() const {
+ std::scoped_lock lock{mutex};
if (is_configuring) {
return {};
}
@@ -1126,6 +1157,7 @@ CaptureButtonState EmulatedController::GetCaptureButtons() const {
}
NpadButtonState EmulatedController::GetNpadButtons() const {
+ std::scoped_lock lock{mutex};
if (is_configuring) {
return {};
}
@@ -1133,6 +1165,7 @@ NpadButtonState EmulatedController::GetNpadButtons() const {
}
DebugPadButton EmulatedController::GetDebugPadButtons() const {
+ std::scoped_lock lock{mutex};
if (is_configuring) {
return {};
}
@@ -1140,20 +1173,27 @@ DebugPadButton EmulatedController::GetDebugPadButtons() const {
}
AnalogSticks EmulatedController::GetSticks() const {
+ std::unique_lock lock{mutex};
+
if (is_configuring) {
return {};
}
+
// Some drivers like stick from buttons need constant refreshing
for (auto& device : stick_devices) {
if (!device) {
continue;
}
+ lock.unlock();
device->SoftUpdate();
+ lock.lock();
}
+
return controller.analog_stick_state;
}
NpadGcTriggerState EmulatedController::GetTriggers() const {
+ std::scoped_lock lock{mutex};
if (is_configuring) {
return {};
}
@@ -1161,26 +1201,35 @@ NpadGcTriggerState EmulatedController::GetTriggers() const {
}
MotionState EmulatedController::GetMotions() const {
+ std::unique_lock lock{mutex};
+
+ // Some drivers like mouse motion need constant refreshing
if (force_update_motion) {
for (auto& device : motion_devices) {
if (!device) {
continue;
}
+ lock.unlock();
device->ForceUpdate();
+ lock.lock();
}
}
+
return controller.motion_state;
}
ControllerColors EmulatedController::GetColors() const {
+ std::scoped_lock lock{mutex};
return controller.colors_state;
}
BatteryLevelState EmulatedController::GetBattery() const {
+ std::scoped_lock lock{mutex};
return controller.battery_state;
}
void EmulatedController::TriggerOnChange(ControllerTriggerType type, bool is_npad_service_update) {
+ std::scoped_lock lock{callback_mutex};
for (const auto& poller_pair : callback_list) {
const ControllerUpdateCallback& poller = poller_pair.second;
if (!is_npad_service_update && poller.is_npad_service) {
@@ -1193,13 +1242,13 @@ void EmulatedController::TriggerOnChange(ControllerTriggerType type, bool is_npa
}
int EmulatedController::SetCallback(ControllerUpdateCallback update_callback) {
- std::lock_guard lock{mutex};
+ std::scoped_lock lock{callback_mutex};
callback_list.insert_or_assign(last_callback_key, std::move(update_callback));
return last_callback_key++;
}
void EmulatedController::DeleteCallback(int key) {
- std::lock_guard lock{mutex};
+ std::scoped_lock lock{callback_mutex};
const auto& iterator = callback_list.find(key);
if (iterator == callback_list.end()) {
LOG_ERROR(Input, "Tried to delete non-existent callback {}", key);
diff --git a/src/core/hid/emulated_controller.h b/src/core/hid/emulated_controller.h
index aa52f9572..1e224685d 100644
--- a/src/core/hid/emulated_controller.h
+++ b/src/core/hid/emulated_controller.h
@@ -400,7 +400,7 @@ private:
*/
void TriggerOnChange(ControllerTriggerType type, bool is_service_update);
- NpadIdType npad_id_type;
+ const NpadIdType npad_id_type;
NpadStyleIndex npad_type{NpadStyleIndex::None};
NpadStyleTag supported_style_tag{NpadStyleSet::All};
bool is_connected{false};
@@ -434,6 +434,7 @@ private:
StickDevices tas_stick_devices;
mutable std::mutex mutex;
+ mutable std::mutex callback_mutex;
std::unordered_map<int, ControllerUpdateCallback> callback_list;
int last_callback_key = 0;
diff --git a/src/core/hid/emulated_devices.cpp b/src/core/hid/emulated_devices.cpp
index 708480f2d..2f84d2b52 100644
--- a/src/core/hid/emulated_devices.cpp
+++ b/src/core/hid/emulated_devices.cpp
@@ -15,6 +15,7 @@ EmulatedDevices::EmulatedDevices() = default;
EmulatedDevices::~EmulatedDevices() = default;
void EmulatedDevices::ReloadFromSettings() {
+ ring_params = Common::ParamPackage(Settings::values.ringcon_analogs);
ReloadInput();
}
@@ -66,6 +67,8 @@ void EmulatedDevices::ReloadInput() {
key_index++;
}
+ ring_analog_device = Common::Input::CreateDevice<Common::Input::InputDevice>(ring_params);
+
for (std::size_t index = 0; index < mouse_button_devices.size(); ++index) {
if (!mouse_button_devices[index]) {
continue;
@@ -120,6 +123,13 @@ void EmulatedDevices::ReloadInput() {
},
});
}
+
+ if (ring_analog_device) {
+ ring_analog_device->SetCallback({
+ .on_change =
+ [this](const Common::Input::CallbackStatus& callback) { SetRingAnalog(callback); },
+ });
+ }
}
void EmulatedDevices::UnloadInput() {
@@ -155,6 +165,7 @@ void EmulatedDevices::SaveCurrentConfig() {
if (!is_configuring) {
return;
}
+ Settings::values.ringcon_analogs = ring_params.Serialize();
}
void EmulatedDevices::RestoreConfig() {
@@ -164,12 +175,21 @@ void EmulatedDevices::RestoreConfig() {
ReloadFromSettings();
}
+Common::ParamPackage EmulatedDevices::GetRingParam() const {
+ return ring_params;
+}
+
+void EmulatedDevices::SetRingParam(Common::ParamPackage param) {
+ ring_params = std::move(param);
+ ReloadInput();
+}
+
void EmulatedDevices::SetKeyboardButton(const Common::Input::CallbackStatus& callback,
std::size_t index) {
if (index >= device_status.keyboard_values.size()) {
return;
}
- std::lock_guard lock{mutex};
+ std::unique_lock lock{mutex};
bool value_changed = false;
const auto new_status = TransformToButton(callback);
auto& current_status = device_status.keyboard_values[index];
@@ -201,6 +221,7 @@ void EmulatedDevices::SetKeyboardButton(const Common::Input::CallbackStatus& cal
}
if (is_configuring) {
+ lock.unlock();
TriggerOnChange(DeviceTriggerType::Keyboard);
return;
}
@@ -208,6 +229,7 @@ void EmulatedDevices::SetKeyboardButton(const Common::Input::CallbackStatus& cal
// Index should be converted from NativeKeyboard to KeyboardKeyIndex
UpdateKey(index, current_status.value);
+ lock.unlock();
TriggerOnChange(DeviceTriggerType::Keyboard);
}
@@ -227,7 +249,7 @@ void EmulatedDevices::SetKeyboardModifier(const Common::Input::CallbackStatus& c
if (index >= device_status.keyboard_moddifier_values.size()) {
return;
}
- std::lock_guard lock{mutex};
+ std::unique_lock lock{mutex};
bool value_changed = false;
const auto new_status = TransformToButton(callback);
auto& current_status = device_status.keyboard_moddifier_values[index];
@@ -259,6 +281,7 @@ void EmulatedDevices::SetKeyboardModifier(const Common::Input::CallbackStatus& c
}
if (is_configuring) {
+ lock.unlock();
TriggerOnChange(DeviceTriggerType::KeyboardModdifier);
return;
}
@@ -289,6 +312,7 @@ void EmulatedDevices::SetKeyboardModifier(const Common::Input::CallbackStatus& c
break;
}
+ lock.unlock();
TriggerOnChange(DeviceTriggerType::KeyboardModdifier);
}
@@ -297,7 +321,7 @@ void EmulatedDevices::SetMouseButton(const Common::Input::CallbackStatus& callba
if (index >= device_status.mouse_button_values.size()) {
return;
}
- std::lock_guard lock{mutex};
+ std::unique_lock lock{mutex};
bool value_changed = false;
const auto new_status = TransformToButton(callback);
auto& current_status = device_status.mouse_button_values[index];
@@ -329,6 +353,7 @@ void EmulatedDevices::SetMouseButton(const Common::Input::CallbackStatus& callba
}
if (is_configuring) {
+ lock.unlock();
TriggerOnChange(DeviceTriggerType::Mouse);
return;
}
@@ -351,6 +376,7 @@ void EmulatedDevices::SetMouseButton(const Common::Input::CallbackStatus& callba
break;
}
+ lock.unlock();
TriggerOnChange(DeviceTriggerType::Mouse);
}
@@ -359,13 +385,14 @@ void EmulatedDevices::SetMouseAnalog(const Common::Input::CallbackStatus& callba
if (index >= device_status.mouse_analog_values.size()) {
return;
}
- std::lock_guard lock{mutex};
+ std::unique_lock lock{mutex};
const auto analog_value = TransformToAnalog(callback);
device_status.mouse_analog_values[index] = analog_value;
if (is_configuring) {
device_status.mouse_position_state = {};
+ lock.unlock();
TriggerOnChange(DeviceTriggerType::Mouse);
return;
}
@@ -379,17 +406,19 @@ void EmulatedDevices::SetMouseAnalog(const Common::Input::CallbackStatus& callba
break;
}
+ lock.unlock();
TriggerOnChange(DeviceTriggerType::Mouse);
}
void EmulatedDevices::SetMouseStick(const Common::Input::CallbackStatus& callback) {
- std::lock_guard lock{mutex};
+ std::unique_lock lock{mutex};
const auto touch_value = TransformToTouch(callback);
device_status.mouse_stick_value = touch_value;
if (is_configuring) {
device_status.mouse_position_state = {};
+ lock.unlock();
TriggerOnChange(DeviceTriggerType::Mouse);
return;
}
@@ -397,42 +426,77 @@ void EmulatedDevices::SetMouseStick(const Common::Input::CallbackStatus& callbac
device_status.mouse_position_state.x = touch_value.x.value;
device_status.mouse_position_state.y = touch_value.y.value;
+ lock.unlock();
TriggerOnChange(DeviceTriggerType::Mouse);
}
+void EmulatedDevices::SetRingAnalog(const Common::Input::CallbackStatus& callback) {
+ std::lock_guard lock{mutex};
+ const auto force_value = TransformToStick(callback);
+
+ device_status.ring_analog_value = force_value.x;
+
+ if (is_configuring) {
+ device_status.ring_analog_value = {};
+ TriggerOnChange(DeviceTriggerType::RingController);
+ return;
+ }
+
+ device_status.ring_analog_state.force = force_value.x.value;
+
+ TriggerOnChange(DeviceTriggerType::RingController);
+}
+
KeyboardValues EmulatedDevices::GetKeyboardValues() const {
+ std::scoped_lock lock{mutex};
return device_status.keyboard_values;
}
KeyboardModifierValues EmulatedDevices::GetKeyboardModdifierValues() const {
+ std::scoped_lock lock{mutex};
return device_status.keyboard_moddifier_values;
}
MouseButtonValues EmulatedDevices::GetMouseButtonsValues() const {
+ std::scoped_lock lock{mutex};
return device_status.mouse_button_values;
}
+RingAnalogValue EmulatedDevices::GetRingSensorValues() const {
+ return device_status.ring_analog_value;
+}
+
KeyboardKey EmulatedDevices::GetKeyboard() const {
+ std::scoped_lock lock{mutex};
return device_status.keyboard_state;
}
KeyboardModifier EmulatedDevices::GetKeyboardModifier() const {
+ std::scoped_lock lock{mutex};
return device_status.keyboard_moddifier_state;
}
MouseButton EmulatedDevices::GetMouseButtons() const {
+ std::scoped_lock lock{mutex};
return device_status.mouse_button_state;
}
MousePosition EmulatedDevices::GetMousePosition() const {
+ std::scoped_lock lock{mutex};
return device_status.mouse_position_state;
}
AnalogStickState EmulatedDevices::GetMouseWheel() const {
+ std::scoped_lock lock{mutex};
return device_status.mouse_wheel_state;
}
+RingSensorForce EmulatedDevices::GetRingSensorForce() const {
+ return device_status.ring_analog_state;
+}
+
void EmulatedDevices::TriggerOnChange(DeviceTriggerType type) {
+ std::scoped_lock lock{callback_mutex};
for (const auto& poller_pair : callback_list) {
const InterfaceUpdateCallback& poller = poller_pair.second;
if (poller.on_change) {
@@ -442,13 +506,13 @@ void EmulatedDevices::TriggerOnChange(DeviceTriggerType type) {
}
int EmulatedDevices::SetCallback(InterfaceUpdateCallback update_callback) {
- std::lock_guard lock{mutex};
+ std::scoped_lock lock{callback_mutex};
callback_list.insert_or_assign(last_callback_key, std::move(update_callback));
return last_callback_key++;
}
void EmulatedDevices::DeleteCallback(int key) {
- std::lock_guard lock{mutex};
+ std::scoped_lock lock{callback_mutex};
const auto& iterator = callback_list.find(key);
if (iterator == callback_list.end()) {
LOG_ERROR(Input, "Tried to delete non-existent callback {}", key);
diff --git a/src/core/hid/emulated_devices.h b/src/core/hid/emulated_devices.h
index 790d3b411..fb6451e7a 100644
--- a/src/core/hid/emulated_devices.h
+++ b/src/core/hid/emulated_devices.h
@@ -26,9 +26,11 @@ using MouseButtonDevices = std::array<std::unique_ptr<Common::Input::InputDevice
using MouseAnalogDevices = std::array<std::unique_ptr<Common::Input::InputDevice>,
Settings::NativeMouseWheel::NumMouseWheels>;
using MouseStickDevice = std::unique_ptr<Common::Input::InputDevice>;
+using RingAnalogDevice = std::unique_ptr<Common::Input::InputDevice>;
using MouseButtonParams =
std::array<Common::ParamPackage, Settings::NativeMouseButton::NumMouseButtons>;
+using RingAnalogParams = Common::ParamPackage;
using KeyboardValues =
std::array<Common::Input::ButtonStatus, Settings::NativeKeyboard::NumKeyboardKeys>;
@@ -39,12 +41,17 @@ using MouseButtonValues =
using MouseAnalogValues =
std::array<Common::Input::AnalogStatus, Settings::NativeMouseWheel::NumMouseWheels>;
using MouseStickValue = Common::Input::TouchStatus;
+using RingAnalogValue = Common::Input::AnalogStatus;
struct MousePosition {
f32 x;
f32 y;
};
+struct RingSensorForce {
+ f32 force;
+};
+
struct DeviceStatus {
// Data from input_common
KeyboardValues keyboard_values{};
@@ -52,6 +59,7 @@ struct DeviceStatus {
MouseButtonValues mouse_button_values{};
MouseAnalogValues mouse_analog_values{};
MouseStickValue mouse_stick_value{};
+ RingAnalogValue ring_analog_value{};
// Data for HID serices
KeyboardKey keyboard_state{};
@@ -59,12 +67,14 @@ struct DeviceStatus {
MouseButton mouse_button_state{};
MousePosition mouse_position_state{};
AnalogStickState mouse_wheel_state{};
+ RingSensorForce ring_analog_state{};
};
enum class DeviceTriggerType {
Keyboard,
KeyboardModdifier,
Mouse,
+ RingController,
};
struct InterfaceUpdateCallback {
@@ -110,6 +120,15 @@ public:
/// Reverts any mapped changes made that weren't saved
void RestoreConfig();
+ // Returns the current mapped ring device
+ Common::ParamPackage GetRingParam() const;
+
+ /**
+ * Updates the current mapped ring device
+ * @param param ParamPackage with ring sensor data to be mapped
+ */
+ void SetRingParam(Common::ParamPackage param);
+
/// Returns the latest status of button input from the keyboard with parameters
KeyboardValues GetKeyboardValues() const;
@@ -119,6 +138,9 @@ public:
/// Returns the latest status of button input from the mouse with parameters
MouseButtonValues GetMouseButtonsValues() const;
+ /// Returns the latest status of analog input from the ring sensor with parameters
+ RingAnalogValue GetRingSensorValues() const;
+
/// Returns the latest status of button input from the keyboard
KeyboardKey GetKeyboard() const;
@@ -134,6 +156,9 @@ public:
/// Returns the latest mouse wheel change
AnalogStickState GetMouseWheel() const;
+ /// Returns the latest ringcon force sensor value
+ RingSensorForce GetRingSensorForce() const;
+
/**
* Adds a callback to the list of events
* @param update_callback InterfaceUpdateCallback that will be triggered
@@ -186,6 +211,12 @@ private:
void SetMouseStick(const Common::Input::CallbackStatus& callback);
/**
+ * Updates the ring analog sensor status of the ring controller
+ * @param callback A CallbackStatus containing the force status
+ */
+ void SetRingAnalog(const Common::Input::CallbackStatus& callback);
+
+ /**
* Triggers a callback that something has changed on the device status
* @param type Input type of the event to trigger
*/
@@ -193,13 +224,17 @@ private:
bool is_configuring{false};
+ RingAnalogParams ring_params;
+
KeyboardDevices keyboard_devices;
KeyboardModifierDevices keyboard_modifier_devices;
MouseButtonDevices mouse_button_devices;
MouseAnalogDevices mouse_analog_devices;
MouseStickDevice mouse_stick_device;
+ RingAnalogDevice ring_analog_device;
mutable std::mutex mutex;
+ mutable std::mutex callback_mutex;
std::unordered_map<int, InterfaceUpdateCallback> callback_list;
int last_callback_key = 0;
diff --git a/src/core/hid/hid_types.h b/src/core/hid/hid_types.h
index 778b328b9..422a9af33 100644
--- a/src/core/hid/hid_types.h
+++ b/src/core/hid/hid_types.h
@@ -491,9 +491,10 @@ struct SixAxisSensorHandle {
};
static_assert(sizeof(SixAxisSensorHandle) == 4, "SixAxisSensorHandle is an invalid size");
+// These parameters seem related to how much gyro/accelerometer is used
struct SixAxisSensorFusionParameters {
- f32 parameter1;
- f32 parameter2;
+ f32 parameter1{0.03f}; // Range 0.0 to 1.0, default 0.03
+ f32 parameter2{0.4f}; // Default 0.4
};
static_assert(sizeof(SixAxisSensorFusionParameters) == 8,
"SixAxisSensorFusionParameters is an invalid size");
diff --git a/src/core/hle/kernel/board/nintendo/nx/k_system_control.cpp b/src/core/hle/kernel/board/nintendo/nx/k_system_control.cpp
index 8027bec00..7765e7848 100644
--- a/src/core/hle/kernel/board/nintendo/nx/k_system_control.cpp
+++ b/src/core/hle/kernel/board/nintendo/nx/k_system_control.cpp
@@ -148,9 +148,9 @@ u64 GenerateUniformRange(u64 min, u64 max, F f) {
} // Anonymous namespace
u64 KSystemControl::GenerateRandomU64() {
- static std::random_device device;
- static std::mt19937 gen(device());
- static std::uniform_int_distribution<u64> distribution(1, std::numeric_limits<u64>::max());
+ std::random_device device;
+ std::mt19937 gen(device());
+ std::uniform_int_distribution<u64> distribution(1, std::numeric_limits<u64>::max());
return distribution(gen);
}
diff --git a/src/core/hle/kernel/global_scheduler_context.h b/src/core/hle/kernel/global_scheduler_context.h
index 6f44b534f..47425a3a1 100644
--- a/src/core/hle/kernel/global_scheduler_context.h
+++ b/src/core/hle/kernel/global_scheduler_context.h
@@ -8,7 +8,6 @@
#include <vector>
#include "common/common_types.h"
-#include "common/spin_lock.h"
#include "core/hardware_properties.h"
#include "core/hle/kernel/k_priority_queue.h"
#include "core/hle/kernel/k_scheduler_lock.h"
@@ -80,7 +79,7 @@ private:
/// Lists all thread ids that aren't deleted/etc.
std::vector<KThread*> thread_list;
- Common::SpinLock global_list_guard{};
+ std::mutex global_list_guard;
};
} // namespace Kernel
diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp
index 42d1b0e31..5828ac923 100644
--- a/src/core/hle/kernel/hle_ipc.cpp
+++ b/src/core/hle/kernel/hle_ipc.cpp
@@ -24,8 +24,15 @@
namespace Kernel {
-SessionRequestHandler::SessionRequestHandler(KernelCore& kernel_, const char* service_name_)
- : kernel{kernel_}, service_thread{kernel.CreateServiceThread(service_name_)} {}
+SessionRequestHandler::SessionRequestHandler(KernelCore& kernel_, const char* service_name_,
+ ServiceThreadType thread_type)
+ : kernel{kernel_} {
+ if (thread_type == ServiceThreadType::CreateNew) {
+ service_thread = kernel.CreateServiceThread(service_name_);
+ } else {
+ service_thread = kernel.GetDefaultServiceThread();
+ }
+}
SessionRequestHandler::~SessionRequestHandler() {
kernel.ReleaseServiceThread(service_thread);
@@ -44,7 +51,7 @@ bool SessionRequestManager::HasSessionRequestHandler(const HLERequestContext& co
LOG_CRITICAL(IPC, "object_id {} is too big!", object_id);
return false;
}
- return DomainHandler(object_id - 1).lock() != nullptr;
+ return !DomainHandler(object_id - 1).expired();
} else {
return session_handler != nullptr;
}
@@ -52,6 +59,9 @@ bool SessionRequestManager::HasSessionRequestHandler(const HLERequestContext& co
void SessionRequestHandler::ClientConnected(KServerSession* session) {
session->ClientConnected(shared_from_this());
+
+ // Ensure our server session is tracked globally.
+ kernel.RegisterServerObject(session);
}
void SessionRequestHandler::ClientDisconnected(KServerSession* session) {
diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h
index 670cc741c..640146137 100644
--- a/src/core/hle/kernel/hle_ipc.h
+++ b/src/core/hle/kernel/hle_ipc.h
@@ -33,6 +33,11 @@ namespace Service {
class ServiceFrameworkBase;
}
+enum class ServiceThreadType {
+ Default,
+ CreateNew,
+};
+
namespace Kernel {
class Domain;
@@ -57,7 +62,8 @@ enum class ThreadWakeupReason;
*/
class SessionRequestHandler : public std::enable_shared_from_this<SessionRequestHandler> {
public:
- SessionRequestHandler(KernelCore& kernel, const char* service_name_);
+ SessionRequestHandler(KernelCore& kernel_, const char* service_name_,
+ ServiceThreadType thread_type);
virtual ~SessionRequestHandler();
/**
diff --git a/src/core/hle/kernel/k_auto_object.h b/src/core/hle/kernel/k_auto_object.h
index 05779f2d5..423e8d8f5 100644
--- a/src/core/hle/kernel/k_auto_object.h
+++ b/src/core/hle/kernel/k_auto_object.h
@@ -89,9 +89,7 @@ public:
explicit KAutoObject(KernelCore& kernel_) : kernel(kernel_) {
RegisterWithKernel();
}
- virtual ~KAutoObject() {
- UnregisterWithKernel();
- }
+ virtual ~KAutoObject() = default;
static KAutoObject* Create(KAutoObject* ptr);
@@ -163,11 +161,12 @@ public:
do {
ASSERT(cur_ref_count > 0);
} while (!m_ref_count.compare_exchange_weak(cur_ref_count, cur_ref_count - 1,
- std::memory_order_relaxed));
+ std::memory_order_acq_rel));
// If ref count hits zero, destroy the object.
if (cur_ref_count - 1 == 0) {
this->Destroy();
+ this->UnregisterWithKernel();
}
}
diff --git a/src/core/hle/kernel/k_code_memory.cpp b/src/core/hle/kernel/k_code_memory.cpp
index b365ce7b7..09eaf004c 100644
--- a/src/core/hle/kernel/k_code_memory.cpp
+++ b/src/core/hle/kernel/k_code_memory.cpp
@@ -28,15 +28,21 @@ ResultCode KCodeMemory::Initialize(Core::DeviceMemory& device_memory, VAddr addr
auto& page_table = m_owner->PageTable();
// Construct the page group.
- m_page_group = KPageLinkedList(addr, Common::DivideUp(size, PageSize));
+ m_page_group =
+ KPageLinkedList(page_table.GetPhysicalAddr(addr), Common::DivideUp(size, PageSize));
// Lock the memory.
R_TRY(page_table.LockForCodeMemory(addr, size))
// Clear the memory.
- for (const auto& block : m_page_group.Nodes()) {
- std::memset(device_memory.GetPointer(block.GetAddress()), 0xFF, block.GetSize());
- }
+ //
+ // FIXME: this ends up clobbering address ranges outside the scope of the mapping within
+ // guest memory, and is not specifically required if the guest program is correctly
+ // written, so disable until this is further investigated.
+ //
+ // for (const auto& block : m_page_group.Nodes()) {
+ // std::memset(device_memory.GetPointer(block.GetAddress()), 0xFF, block.GetSize());
+ // }
// Set remaining tracking members.
m_address = addr;
diff --git a/src/core/hle/kernel/k_page_linked_list.h b/src/core/hle/kernel/k_page_linked_list.h
index 0e2ae582a..869228322 100644
--- a/src/core/hle/kernel/k_page_linked_list.h
+++ b/src/core/hle/kernel/k_page_linked_list.h
@@ -89,6 +89,10 @@ public:
return ResultSuccess;
}
+ bool Empty() const {
+ return nodes.empty();
+ }
+
private:
std::list<Node> nodes;
};
diff --git a/src/core/hle/kernel/k_page_table.cpp b/src/core/hle/kernel/k_page_table.cpp
index 02d93b12e..47ea3c89c 100644
--- a/src/core/hle/kernel/k_page_table.cpp
+++ b/src/core/hle/kernel/k_page_table.cpp
@@ -346,7 +346,8 @@ ResultCode KPageTable::MapCodeMemory(VAddr dst_address, VAddr src_address, std::
return ResultSuccess;
}
-ResultCode KPageTable::UnmapCodeMemory(VAddr dst_address, VAddr src_address, std::size_t size) {
+ResultCode KPageTable::UnmapCodeMemory(VAddr dst_address, VAddr src_address, std::size_t size,
+ ICacheInvalidationStrategy icache_invalidation_strategy) {
// Validate the mapping request.
R_UNLESS(this->CanContain(dst_address, size, KMemoryState::AliasCode),
ResultInvalidMemoryRegion);
@@ -396,7 +397,11 @@ ResultCode KPageTable::UnmapCodeMemory(VAddr dst_address, VAddr src_address, std
bool reprotected_pages = false;
SCOPE_EXIT({
if (reprotected_pages && any_code_pages) {
- system.InvalidateCpuInstructionCacheRange(dst_address, size);
+ if (icache_invalidation_strategy == ICacheInvalidationStrategy::InvalidateRange) {
+ system.InvalidateCpuInstructionCacheRange(dst_address, size);
+ } else {
+ system.InvalidateCpuInstructionCaches();
+ }
}
});
@@ -486,6 +491,58 @@ VAddr KPageTable::FindFreeArea(VAddr region_start, std::size_t region_num_pages,
return address;
}
+ResultCode KPageTable::MakePageGroup(KPageLinkedList& pg, VAddr addr, size_t num_pages) {
+ ASSERT(this->IsLockedByCurrentThread());
+
+ const size_t size = num_pages * PageSize;
+
+ // We're making a new group, not adding to an existing one.
+ R_UNLESS(pg.Empty(), ResultInvalidCurrentMemory);
+
+ // Begin traversal.
+ Common::PageTable::TraversalContext context;
+ Common::PageTable::TraversalEntry next_entry;
+ R_UNLESS(page_table_impl.BeginTraversal(next_entry, context, addr), ResultInvalidCurrentMemory);
+
+ // Prepare tracking variables.
+ PAddr cur_addr = next_entry.phys_addr;
+ size_t cur_size = next_entry.block_size - (cur_addr & (next_entry.block_size - 1));
+ size_t tot_size = cur_size;
+
+ // Iterate, adding to group as we go.
+ const auto& memory_layout = system.Kernel().MemoryLayout();
+ while (tot_size < size) {
+ R_UNLESS(page_table_impl.ContinueTraversal(next_entry, context),
+ ResultInvalidCurrentMemory);
+
+ if (next_entry.phys_addr != (cur_addr + cur_size)) {
+ const size_t cur_pages = cur_size / PageSize;
+
+ R_UNLESS(IsHeapPhysicalAddress(memory_layout, cur_addr), ResultInvalidCurrentMemory);
+ R_TRY(pg.AddBlock(cur_addr, cur_pages));
+
+ cur_addr = next_entry.phys_addr;
+ cur_size = next_entry.block_size;
+ } else {
+ cur_size += next_entry.block_size;
+ }
+
+ tot_size += next_entry.block_size;
+ }
+
+ // Ensure we add the right amount for the last block.
+ if (tot_size > size) {
+ cur_size -= (tot_size - size);
+ }
+
+ // Add the last block.
+ const size_t cur_pages = cur_size / PageSize;
+ R_UNLESS(IsHeapPhysicalAddress(memory_layout, cur_addr), ResultInvalidCurrentMemory);
+ R_TRY(pg.AddBlock(cur_addr, cur_pages));
+
+ return ResultSuccess;
+}
+
ResultCode KPageTable::UnmapProcessMemory(VAddr dst_addr, std::size_t size,
KPageTable& src_page_table, VAddr src_addr) {
KScopedLightLock lk(general_lock);
@@ -511,6 +568,8 @@ ResultCode KPageTable::UnmapProcessMemory(VAddr dst_addr, std::size_t size,
block_manager->Update(dst_addr, num_pages, KMemoryState::Free, KMemoryPermission::None,
KMemoryAttribute::None);
+ system.InvalidateCpuInstructionCaches();
+
return ResultSuccess;
}
@@ -1223,6 +1282,31 @@ ResultCode KPageTable::UnmapPages(VAddr address, std::size_t num_pages, KMemoryS
return ResultSuccess;
}
+ResultCode KPageTable::MakeAndOpenPageGroup(KPageLinkedList* out, VAddr address, size_t num_pages,
+ KMemoryState state_mask, KMemoryState state,
+ KMemoryPermission perm_mask, KMemoryPermission perm,
+ KMemoryAttribute attr_mask, KMemoryAttribute attr) {
+ // Ensure that the page group isn't null.
+ ASSERT(out != nullptr);
+
+ // Make sure that the region we're mapping is valid for the table.
+ const size_t size = num_pages * PageSize;
+ R_UNLESS(this->Contains(address, size), ResultInvalidCurrentMemory);
+
+ // Lock the table.
+ KScopedLightLock lk(general_lock);
+
+ // Check if state allows us to create the group.
+ R_TRY(this->CheckMemoryState(address, size, state_mask | KMemoryState::FlagReferenceCounted,
+ state | KMemoryState::FlagReferenceCounted, perm_mask, perm,
+ attr_mask, attr));
+
+ // Create a new page group for the region.
+ R_TRY(this->MakePageGroup(*out, address, num_pages));
+
+ return ResultSuccess;
+}
+
ResultCode KPageTable::SetProcessMemoryPermission(VAddr addr, std::size_t size,
Svc::MemoryPermission svc_perm) {
const size_t num_pages = size / PageSize;
@@ -1605,57 +1689,21 @@ ResultCode KPageTable::UnlockForDeviceAddressSpace(VAddr addr, std::size_t size)
}
ResultCode KPageTable::LockForCodeMemory(VAddr addr, std::size_t size) {
- KScopedLightLock lk(general_lock);
-
- KMemoryPermission new_perm = KMemoryPermission::NotMapped | KMemoryPermission::KernelReadWrite;
-
- KMemoryPermission old_perm{};
-
- if (const ResultCode result{CheckMemoryState(
- nullptr, &old_perm, nullptr, nullptr, addr, size, KMemoryState::FlagCanCodeMemory,
- KMemoryState::FlagCanCodeMemory, KMemoryPermission::All,
- KMemoryPermission::UserReadWrite, KMemoryAttribute::All, KMemoryAttribute::None)};
- result.IsError()) {
- return result;
- }
-
- new_perm = (new_perm != KMemoryPermission::None) ? new_perm : old_perm;
-
- block_manager->UpdateLock(
- addr, size / PageSize,
- [](KMemoryBlockManager::iterator block, KMemoryPermission permission) {
- block->ShareToDevice(permission);
- },
- new_perm);
-
- return ResultSuccess;
+ return this->LockMemoryAndOpen(
+ nullptr, nullptr, addr, size, KMemoryState::FlagCanCodeMemory,
+ KMemoryState::FlagCanCodeMemory, KMemoryPermission::All, KMemoryPermission::UserReadWrite,
+ KMemoryAttribute::All, KMemoryAttribute::None,
+ static_cast<KMemoryPermission>(KMemoryPermission::NotMapped |
+ KMemoryPermission::KernelReadWrite),
+ KMemoryAttribute::Locked);
}
ResultCode KPageTable::UnlockForCodeMemory(VAddr addr, std::size_t size) {
- KScopedLightLock lk(general_lock);
-
- KMemoryPermission new_perm = KMemoryPermission::UserReadWrite;
-
- KMemoryPermission old_perm{};
-
- if (const ResultCode result{CheckMemoryState(
- nullptr, &old_perm, nullptr, nullptr, addr, size, KMemoryState::FlagCanCodeMemory,
- KMemoryState::FlagCanCodeMemory, KMemoryPermission::None, KMemoryPermission::None,
- KMemoryAttribute::All, KMemoryAttribute::Locked)};
- result.IsError()) {
- return result;
- }
-
- new_perm = (new_perm != KMemoryPermission::None) ? new_perm : old_perm;
-
- block_manager->UpdateLock(
- addr, size / PageSize,
- [](KMemoryBlockManager::iterator block, KMemoryPermission permission) {
- block->UnshareToDevice(permission);
- },
- new_perm);
-
- return ResultSuccess;
+ return this->UnlockMemory(addr, size, KMemoryState::FlagCanCodeMemory,
+ KMemoryState::FlagCanCodeMemory, KMemoryPermission::None,
+ KMemoryPermission::None, KMemoryAttribute::All,
+ KMemoryAttribute::Locked, KMemoryPermission::UserReadWrite,
+ KMemoryAttribute::Locked, nullptr);
}
ResultCode KPageTable::InitializeMemoryLayout(VAddr start, VAddr end) {
@@ -1991,4 +2039,109 @@ ResultCode KPageTable::CheckMemoryState(KMemoryState* out_state, KMemoryPermissi
return ResultSuccess;
}
+ResultCode KPageTable::LockMemoryAndOpen(KPageLinkedList* out_pg, PAddr* out_paddr, VAddr addr,
+ size_t size, KMemoryState state_mask, KMemoryState state,
+ KMemoryPermission perm_mask, KMemoryPermission perm,
+ KMemoryAttribute attr_mask, KMemoryAttribute attr,
+ KMemoryPermission new_perm, KMemoryAttribute lock_attr) {
+ // Validate basic preconditions.
+ ASSERT((lock_attr & attr) == KMemoryAttribute::None);
+ ASSERT((lock_attr & (KMemoryAttribute::IpcLocked | KMemoryAttribute::DeviceShared)) ==
+ KMemoryAttribute::None);
+
+ // Validate the lock request.
+ const size_t num_pages = size / PageSize;
+ R_UNLESS(this->Contains(addr, size), ResultInvalidCurrentMemory);
+
+ // Lock the table.
+ KScopedLightLock lk(general_lock);
+
+ // Check that the output page group is empty, if it exists.
+ if (out_pg) {
+ ASSERT(out_pg->GetNumPages() == 0);
+ }
+
+ // Check the state.
+ KMemoryState old_state{};
+ KMemoryPermission old_perm{};
+ KMemoryAttribute old_attr{};
+ size_t num_allocator_blocks{};
+ R_TRY(this->CheckMemoryState(std::addressof(old_state), std::addressof(old_perm),
+ std::addressof(old_attr), std::addressof(num_allocator_blocks),
+ addr, size, state_mask | KMemoryState::FlagReferenceCounted,
+ state | KMemoryState::FlagReferenceCounted, perm_mask, perm,
+ attr_mask, attr));
+
+ // Get the physical address, if we're supposed to.
+ if (out_paddr != nullptr) {
+ ASSERT(this->GetPhysicalAddressLocked(out_paddr, addr));
+ }
+
+ // Make the page group, if we're supposed to.
+ if (out_pg != nullptr) {
+ R_TRY(this->MakePageGroup(*out_pg, addr, num_pages));
+ }
+
+ // Decide on new perm and attr.
+ new_perm = (new_perm != KMemoryPermission::None) ? new_perm : old_perm;
+ KMemoryAttribute new_attr = static_cast<KMemoryAttribute>(old_attr | lock_attr);
+
+ // Update permission, if we need to.
+ if (new_perm != old_perm) {
+ R_TRY(Operate(addr, num_pages, new_perm, OperationType::ChangePermissions));
+ }
+
+ // Apply the memory block updates.
+ block_manager->Update(addr, num_pages, old_state, new_perm, new_attr);
+
+ return ResultSuccess;
+}
+
+ResultCode KPageTable::UnlockMemory(VAddr addr, size_t size, KMemoryState state_mask,
+ KMemoryState state, KMemoryPermission perm_mask,
+ KMemoryPermission perm, KMemoryAttribute attr_mask,
+ KMemoryAttribute attr, KMemoryPermission new_perm,
+ KMemoryAttribute lock_attr, const KPageLinkedList* pg) {
+ // Validate basic preconditions.
+ ASSERT((attr_mask & lock_attr) == lock_attr);
+ ASSERT((attr & lock_attr) == lock_attr);
+
+ // Validate the unlock request.
+ const size_t num_pages = size / PageSize;
+ R_UNLESS(this->Contains(addr, size), ResultInvalidCurrentMemory);
+
+ // Lock the table.
+ KScopedLightLock lk(general_lock);
+
+ // Check the state.
+ KMemoryState old_state{};
+ KMemoryPermission old_perm{};
+ KMemoryAttribute old_attr{};
+ size_t num_allocator_blocks{};
+ R_TRY(this->CheckMemoryState(std::addressof(old_state), std::addressof(old_perm),
+ std::addressof(old_attr), std::addressof(num_allocator_blocks),
+ addr, size, state_mask | KMemoryState::FlagReferenceCounted,
+ state | KMemoryState::FlagReferenceCounted, perm_mask, perm,
+ attr_mask, attr));
+
+ // Check the page group.
+ if (pg != nullptr) {
+ UNIMPLEMENTED_MSG("PageGroup support is unimplemented!");
+ }
+
+ // Decide on new perm and attr.
+ new_perm = (new_perm != KMemoryPermission::None) ? new_perm : old_perm;
+ KMemoryAttribute new_attr = static_cast<KMemoryAttribute>(old_attr & ~lock_attr);
+
+ // Update permission, if we need to.
+ if (new_perm != old_perm) {
+ R_TRY(Operate(addr, num_pages, new_perm, OperationType::ChangePermissions));
+ }
+
+ // Apply the memory block updates.
+ block_manager->Update(addr, num_pages, old_state, new_perm, new_attr);
+
+ return ResultSuccess;
+}
+
} // namespace Kernel
diff --git a/src/core/hle/kernel/k_page_table.h b/src/core/hle/kernel/k_page_table.h
index 54c6adf8d..dd6022975 100644
--- a/src/core/hle/kernel/k_page_table.h
+++ b/src/core/hle/kernel/k_page_table.h
@@ -12,6 +12,7 @@
#include "core/file_sys/program_metadata.h"
#include "core/hle/kernel/k_light_lock.h"
#include "core/hle/kernel/k_memory_block.h"
+#include "core/hle/kernel/k_memory_layout.h"
#include "core/hle/kernel/k_memory_manager.h"
#include "core/hle/result.h"
@@ -25,6 +26,8 @@ class KMemoryBlockManager;
class KPageTable final {
public:
+ enum class ICacheInvalidationStrategy : u32 { InvalidateRange, InvalidateAll };
+
YUZU_NON_COPYABLE(KPageTable);
YUZU_NON_MOVEABLE(KPageTable);
@@ -37,7 +40,8 @@ public:
ResultCode MapProcessCode(VAddr addr, std::size_t pages_count, KMemoryState state,
KMemoryPermission perm);
ResultCode MapCodeMemory(VAddr dst_address, VAddr src_address, std::size_t size);
- ResultCode UnmapCodeMemory(VAddr dst_address, VAddr src_address, std::size_t size);
+ ResultCode UnmapCodeMemory(VAddr dst_address, VAddr src_address, std::size_t size,
+ ICacheInvalidationStrategy icache_invalidation_strategy);
ResultCode UnmapProcessMemory(VAddr dst_addr, std::size_t size, KPageTable& src_page_table,
VAddr src_addr);
ResultCode MapPhysicalMemory(VAddr addr, std::size_t size);
@@ -71,6 +75,10 @@ public:
ResultCode UnlockForDeviceAddressSpace(VAddr addr, std::size_t size);
ResultCode LockForCodeMemory(VAddr addr, std::size_t size);
ResultCode UnlockForCodeMemory(VAddr addr, std::size_t size);
+ ResultCode MakeAndOpenPageGroup(KPageLinkedList* out, VAddr address, size_t num_pages,
+ KMemoryState state_mask, KMemoryState state,
+ KMemoryPermission perm_mask, KMemoryPermission perm,
+ KMemoryAttribute attr_mask, KMemoryAttribute attr);
Common::PageTable& PageTableImpl() {
return page_table_impl;
@@ -159,10 +167,37 @@ private:
attr_mask, attr, ignore_attr);
}
+ ResultCode LockMemoryAndOpen(KPageLinkedList* out_pg, PAddr* out_paddr, VAddr addr, size_t size,
+ KMemoryState state_mask, KMemoryState state,
+ KMemoryPermission perm_mask, KMemoryPermission perm,
+ KMemoryAttribute attr_mask, KMemoryAttribute attr,
+ KMemoryPermission new_perm, KMemoryAttribute lock_attr);
+ ResultCode UnlockMemory(VAddr addr, size_t size, KMemoryState state_mask, KMemoryState state,
+ KMemoryPermission perm_mask, KMemoryPermission perm,
+ KMemoryAttribute attr_mask, KMemoryAttribute attr,
+ KMemoryPermission new_perm, KMemoryAttribute lock_attr,
+ const KPageLinkedList* pg);
+
+ ResultCode MakePageGroup(KPageLinkedList& pg, VAddr addr, size_t num_pages);
+
bool IsLockedByCurrentThread() const {
return general_lock.IsLockedByCurrentThread();
}
+ bool IsHeapPhysicalAddress(const KMemoryLayout& layout, PAddr phys_addr) {
+ ASSERT(this->IsLockedByCurrentThread());
+
+ return layout.IsHeapPhysicalAddress(cached_physical_heap_region, phys_addr);
+ }
+
+ bool GetPhysicalAddressLocked(PAddr* out, VAddr virt_addr) const {
+ ASSERT(this->IsLockedByCurrentThread());
+
+ *out = GetPhysicalAddr(virt_addr);
+
+ return *out != 0;
+ }
+
mutable KLightLock general_lock;
mutable KLightLock map_physical_memory_lock;
@@ -322,6 +357,7 @@ private:
bool is_aslr_enabled{};
u32 heap_fill_value{};
+ const KMemoryRegion* cached_physical_heap_region{};
KMemoryManager::Pool memory_pool{KMemoryManager::Pool::Application};
KMemoryManager::Direction allocation_option{KMemoryManager::Direction::FromFront};
diff --git a/src/core/hle/kernel/k_process.h b/src/core/hle/kernel/k_process.h
index 48b17fc74..9f171e3da 100644
--- a/src/core/hle/kernel/k_process.h
+++ b/src/core/hle/kernel/k_process.h
@@ -422,7 +422,7 @@ private:
bool is_64bit_process = true;
/// Total running time for the process in ticks.
- u64 total_process_running_time_ticks = 0;
+ std::atomic<u64> total_process_running_time_ticks = 0;
/// Per-process handle table for storing created object handles in.
KHandleTable handle_table;
diff --git a/src/core/hle/kernel/k_scheduler.cpp b/src/core/hle/kernel/k_scheduler.cpp
index 6c0bb1672..526eb4b70 100644
--- a/src/core/hle/kernel/k_scheduler.cpp
+++ b/src/core/hle/kernel/k_scheduler.cpp
@@ -705,7 +705,7 @@ void KScheduler::Unload(KThread* thread) {
prev_thread = nullptr;
}
- thread->context_guard.Unlock();
+ thread->context_guard.unlock();
}
void KScheduler::Reload(KThread* thread) {
@@ -794,13 +794,13 @@ void KScheduler::SwitchToCurrent() {
do {
auto next_thread = current_thread.load();
if (next_thread != nullptr) {
- const auto locked = next_thread->context_guard.TryLock();
+ const auto locked = next_thread->context_guard.try_lock();
if (state.needs_scheduling.load()) {
- next_thread->context_guard.Unlock();
+ next_thread->context_guard.unlock();
break;
}
if (next_thread->GetActiveCore() != core_id) {
- next_thread->context_guard.Unlock();
+ next_thread->context_guard.unlock();
break;
}
if (!locked) {
diff --git a/src/core/hle/kernel/k_scheduler_lock.h b/src/core/hle/kernel/k_scheduler_lock.h
index 93c47f1b1..016e0a818 100644
--- a/src/core/hle/kernel/k_scheduler_lock.h
+++ b/src/core/hle/kernel/k_scheduler_lock.h
@@ -4,6 +4,7 @@
#pragma once
+#include <atomic>
#include "common/assert.h"
#include "core/hle/kernel/k_spin_lock.h"
#include "core/hle/kernel/k_thread.h"
@@ -75,7 +76,7 @@ private:
KernelCore& kernel;
KAlignedSpinLock spin_lock{};
s32 lock_count{};
- KThread* owner_thread{};
+ std::atomic<KThread*> owner_thread{};
};
} // namespace Kernel
diff --git a/src/core/hle/kernel/k_server_port.cpp b/src/core/hle/kernel/k_server_port.cpp
index 433fc98e1..e66c0c992 100644
--- a/src/core/hle/kernel/k_server_port.cpp
+++ b/src/core/hle/kernel/k_server_port.cpp
@@ -62,6 +62,12 @@ void KServerPort::Destroy() {
// Close our reference to our parent.
parent->Close();
+
+ // Release host emulation members.
+ session_handler.reset();
+
+ // Ensure that the global list tracking server objects does not hold on to a reference.
+ kernel.UnregisterServerObject(this);
}
bool KServerPort::IsSignaled() const {
diff --git a/src/core/hle/kernel/k_server_session.cpp b/src/core/hle/kernel/k_server_session.cpp
index 30c56ff29..7ac2ef254 100644
--- a/src/core/hle/kernel/k_server_session.cpp
+++ b/src/core/hle/kernel/k_server_session.cpp
@@ -49,6 +49,9 @@ void KServerSession::Destroy() {
// Release host emulation members.
manager.reset();
+
+ // Ensure that the global list tracking server objects does not hold on to a reference.
+ kernel.UnregisterServerObject(this);
}
void KServerSession::OnClientClosed() {
diff --git a/src/core/hle/kernel/k_spin_lock.cpp b/src/core/hle/kernel/k_spin_lock.cpp
index 4412aa4bb..527ff0f9f 100644
--- a/src/core/hle/kernel/k_spin_lock.cpp
+++ b/src/core/hle/kernel/k_spin_lock.cpp
@@ -4,51 +4,18 @@
#include "core/hle/kernel/k_spin_lock.h"
-#if _MSC_VER
-#include <intrin.h>
-#if _M_AMD64
-#define __x86_64__ 1
-#endif
-#if _M_ARM64
-#define __aarch64__ 1
-#endif
-#else
-#if __x86_64__
-#include <xmmintrin.h>
-#endif
-#endif
-
-namespace {
-
-void ThreadPause() {
-#if __x86_64__
- _mm_pause();
-#elif __aarch64__ && _MSC_VER
- __yield();
-#elif __aarch64__
- asm("yield");
-#endif
-}
-
-} // namespace
-
namespace Kernel {
void KSpinLock::Lock() {
- while (lck.test_and_set(std::memory_order_acquire)) {
- ThreadPause();
- }
+ lck.lock();
}
void KSpinLock::Unlock() {
- lck.clear(std::memory_order_release);
+ lck.unlock();
}
bool KSpinLock::TryLock() {
- if (lck.test_and_set(std::memory_order_acquire)) {
- return false;
- }
- return true;
+ return lck.try_lock();
}
} // namespace Kernel
diff --git a/src/core/hle/kernel/k_spin_lock.h b/src/core/hle/kernel/k_spin_lock.h
index 4d87d006a..7868b25a5 100644
--- a/src/core/hle/kernel/k_spin_lock.h
+++ b/src/core/hle/kernel/k_spin_lock.h
@@ -4,7 +4,7 @@
#pragma once
-#include <atomic>
+#include <mutex>
#include "core/hle/kernel/k_scoped_lock.h"
@@ -25,7 +25,7 @@ public:
[[nodiscard]] bool TryLock();
private:
- std::atomic_flag lck = ATOMIC_FLAG_INIT;
+ std::mutex lck;
};
// TODO(bunnei): Alias for now, in case we want to implement these accurately in the future.
diff --git a/src/core/hle/kernel/k_thread.cpp b/src/core/hle/kernel/k_thread.cpp
index 94c8faf68..af71987e8 100644
--- a/src/core/hle/kernel/k_thread.cpp
+++ b/src/core/hle/kernel/k_thread.cpp
@@ -723,10 +723,10 @@ void KThread::UpdateState() {
ASSERT(kernel.GlobalSchedulerContext().IsLocked());
// Set our suspend flags in state.
- const auto old_state = thread_state;
+ const ThreadState old_state = thread_state.load(std::memory_order_relaxed);
const auto new_state =
static_cast<ThreadState>(this->GetSuspendFlags()) | (old_state & ThreadState::Mask);
- thread_state = new_state;
+ thread_state.store(new_state, std::memory_order_relaxed);
// Note the state change in scheduler.
if (new_state != old_state) {
@@ -738,8 +738,8 @@ void KThread::Continue() {
ASSERT(kernel.GlobalSchedulerContext().IsLocked());
// Clear our suspend flags in state.
- const auto old_state = thread_state;
- thread_state = old_state & ThreadState::Mask;
+ const ThreadState old_state = thread_state.load(std::memory_order_relaxed);
+ thread_state.store(old_state & ThreadState::Mask, std::memory_order_relaxed);
// Note the state change in scheduler.
KScheduler::OnThreadStateChanged(kernel, this, old_state);
@@ -1079,17 +1079,10 @@ void KThread::IfDummyThreadTryWait() {
return;
}
- // Block until we can grab the lock.
- KScopedSpinLock lk{dummy_wait_lock};
-}
-
-void KThread::IfDummyThreadBeginWait() {
- if (!IsDummyThread()) {
- return;
- }
-
- // Ensure the thread will block when IfDummyThreadTryWait is called.
- dummy_wait_lock.Lock();
+ // Block until we are no longer waiting.
+ std::unique_lock lk(dummy_wait_lock);
+ dummy_wait_cv.wait(
+ lk, [&] { return GetState() != ThreadState::Waiting || kernel.IsShuttingDown(); });
}
void KThread::IfDummyThreadEndWait() {
@@ -1097,8 +1090,8 @@ void KThread::IfDummyThreadEndWait() {
return;
}
- // Ensure the thread will no longer block.
- dummy_wait_lock.Unlock();
+ // Wake up the waiting thread.
+ dummy_wait_cv.notify_one();
}
void KThread::BeginWait(KThreadQueue* queue) {
@@ -1107,9 +1100,6 @@ void KThread::BeginWait(KThreadQueue* queue) {
// Set our wait queue.
wait_queue = queue;
-
- // Special case for dummy threads to ensure they block.
- IfDummyThreadBeginWait();
}
void KThread::NotifyAvailable(KSynchronizationObject* signaled_object, ResultCode wait_result_) {
@@ -1158,10 +1148,11 @@ void KThread::SetState(ThreadState state) {
SetMutexWaitAddressForDebugging({});
SetWaitReasonForDebugging({});
- const ThreadState old_state = thread_state;
- thread_state =
- static_cast<ThreadState>((old_state & ~ThreadState::Mask) | (state & ThreadState::Mask));
- if (thread_state != old_state) {
+ const ThreadState old_state = thread_state.load(std::memory_order_relaxed);
+ thread_state.store(
+ static_cast<ThreadState>((old_state & ~ThreadState::Mask) | (state & ThreadState::Mask)),
+ std::memory_order_relaxed);
+ if (thread_state.load(std::memory_order_relaxed) != old_state) {
KScheduler::OnThreadStateChanged(kernel, this, old_state);
}
}
diff --git a/src/core/hle/kernel/k_thread.h b/src/core/hle/kernel/k_thread.h
index f46db7298..4892fdf76 100644
--- a/src/core/hle/kernel/k_thread.h
+++ b/src/core/hle/kernel/k_thread.h
@@ -5,6 +5,9 @@
#pragma once
#include <array>
+#include <atomic>
+#include <condition_variable>
+#include <mutex>
#include <span>
#include <string>
#include <utility>
@@ -14,6 +17,7 @@
#include "common/common_types.h"
#include "common/intrusive_red_black_tree.h"
+#include "common/spin_lock.h"
#include "core/arm/arm_interface.h"
#include "core/hle/kernel/k_affinity_mask.h"
#include "core/hle/kernel/k_light_lock.h"
@@ -255,11 +259,11 @@ public:
[[nodiscard]] std::shared_ptr<Common::Fiber>& GetHostContext();
[[nodiscard]] ThreadState GetState() const {
- return thread_state & ThreadState::Mask;
+ return thread_state.load(std::memory_order_relaxed) & ThreadState::Mask;
}
[[nodiscard]] ThreadState GetRawState() const {
- return thread_state;
+ return thread_state.load(std::memory_order_relaxed);
}
void SetState(ThreadState state);
@@ -641,7 +645,6 @@ public:
// blocking as needed.
void IfDummyThreadTryWait();
- void IfDummyThreadBeginWait();
void IfDummyThreadEndWait();
private:
@@ -751,7 +754,7 @@ private:
KAffinityMask original_physical_affinity_mask{};
s32 original_physical_ideal_core_id{};
s32 num_core_migration_disables{};
- ThreadState thread_state{};
+ std::atomic<ThreadState> thread_state{};
std::atomic<bool> termination_requested{};
bool wait_cancelled{};
bool cancellable{};
@@ -761,13 +764,14 @@ private:
s8 priority_inheritance_count{};
bool resource_limit_release_hint{};
StackParameters stack_parameters{};
- KSpinLock context_guard{};
- KSpinLock dummy_wait_lock{};
+ Common::SpinLock context_guard{};
// For emulation
std::shared_ptr<Common::Fiber> host_context{};
bool is_single_core{};
ThreadType thread_type{};
+ std::mutex dummy_wait_lock;
+ std::condition_variable dummy_wait_cv;
// For debugging
std::vector<KSynchronizationObject*> wait_objects_for_debugging;
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp
index 34da7c23b..5984afd7e 100644
--- a/src/core/hle/kernel/kernel.cpp
+++ b/src/core/hle/kernel/kernel.cpp
@@ -61,6 +61,7 @@ struct KernelCore::Impl {
global_scheduler_context = std::make_unique<Kernel::GlobalSchedulerContext>(kernel);
global_handle_table = std::make_unique<Kernel::KHandleTable>(kernel);
global_handle_table->Initialize(KHandleTable::MaxTableSize);
+ default_service_thread = CreateServiceThread(kernel, "DefaultServiceThread");
is_phantom_mode_for_singlecore = false;
@@ -84,7 +85,7 @@ struct KernelCore::Impl {
void InitializeCores() {
for (u32 core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) {
- cores[core_id].Initialize(current_process->Is64BitProcess());
+ cores[core_id].Initialize((*current_process).Is64BitProcess());
system.Memory().SetCurrentPageTable(*current_process, core_id);
}
}
@@ -95,15 +96,15 @@ struct KernelCore::Impl {
process_list.clear();
- // Close all open server ports.
- std::unordered_set<KServerPort*> server_ports_;
+ // Close all open server sessions and ports.
+ std::unordered_set<KAutoObject*> server_objects_;
{
- std::lock_guard lk(server_ports_lock);
- server_ports_ = server_ports;
- server_ports.clear();
+ std::scoped_lock lk(server_objects_lock);
+ server_objects_ = server_objects;
+ server_objects.clear();
}
- for (auto* server_port : server_ports_) {
- server_port->Close();
+ for (auto* server_object : server_objects_) {
+ server_object->Close();
}
// Ensures all service threads gracefully shutdown.
@@ -139,6 +140,7 @@ struct KernelCore::Impl {
CleanupObject(font_shared_mem);
CleanupObject(irs_shared_mem);
CleanupObject(time_shared_mem);
+ CleanupObject(hidbus_shared_mem);
CleanupObject(system_resource_limit);
for (u32 core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) {
@@ -156,7 +158,7 @@ struct KernelCore::Impl {
// Close kernel objects that were not freed on shutdown
{
- std::lock_guard lk(registered_in_use_objects_lock);
+ std::scoped_lock lk{registered_in_use_objects_lock};
if (registered_in_use_objects.size()) {
for (auto& object : registered_in_use_objects) {
object->Close();
@@ -167,17 +169,17 @@ struct KernelCore::Impl {
// Shutdown all processes.
if (current_process) {
- current_process->Finalize();
+ (*current_process).Finalize();
// current_process->Close();
// TODO: The current process should be destroyed based on accurate ref counting after
// calling Close(). Adding a manual Destroy() call instead to avoid a memory leak.
- current_process->Destroy();
+ (*current_process).Destroy();
current_process = nullptr;
}
// Track kernel objects that were not freed on shutdown
{
- std::lock_guard lk(registered_objects_lock);
+ std::scoped_lock lk{registered_objects_lock};
if (registered_objects.size()) {
LOG_DEBUG(Kernel, "{} kernel objects were dangling on shutdown!",
registered_objects.size());
@@ -621,16 +623,20 @@ struct KernelCore::Impl {
constexpr std::size_t font_size{0x1100000};
constexpr std::size_t irs_size{0x8000};
constexpr std::size_t time_size{0x1000};
+ constexpr std::size_t hidbus_size{0x1000};
const PAddr hid_phys_addr{system_pool.GetAddress()};
const PAddr font_phys_addr{system_pool.GetAddress() + hid_size};
const PAddr irs_phys_addr{system_pool.GetAddress() + hid_size + font_size};
const PAddr time_phys_addr{system_pool.GetAddress() + hid_size + font_size + irs_size};
+ const PAddr hidbus_phys_addr{system_pool.GetAddress() + hid_size + font_size + irs_size +
+ time_size};
hid_shared_mem = KSharedMemory::Create(system.Kernel());
font_shared_mem = KSharedMemory::Create(system.Kernel());
irs_shared_mem = KSharedMemory::Create(system.Kernel());
time_shared_mem = KSharedMemory::Create(system.Kernel());
+ hidbus_shared_mem = KSharedMemory::Create(system.Kernel());
hid_shared_mem->Initialize(system.DeviceMemory(), nullptr,
{hid_phys_addr, hid_size / PageSize},
@@ -648,6 +654,10 @@ struct KernelCore::Impl {
{time_phys_addr, time_size / PageSize},
Svc::MemoryPermission::None, Svc::MemoryPermission::Read,
time_phys_addr, time_size, "Time:SharedMemory");
+ hidbus_shared_mem->Initialize(system.DeviceMemory(), nullptr,
+ {hidbus_phys_addr, hidbus_size / PageSize},
+ Svc::MemoryPermission::None, Svc::MemoryPermission::Read,
+ hidbus_phys_addr, hidbus_size, "HidBus:SharedMemory");
}
KClientPort* CreateNamedServicePort(std::string name) {
@@ -658,13 +668,20 @@ struct KernelCore::Impl {
}
KClientPort* port = &search->second(system.ServiceManager(), system);
- {
- std::lock_guard lk(server_ports_lock);
- server_ports.insert(&port->GetParent()->GetServerPort());
- }
+ RegisterServerObject(&port->GetParent()->GetServerPort());
return port;
}
+ void RegisterServerObject(KAutoObject* server_object) {
+ std::scoped_lock lk(server_objects_lock);
+ server_objects.insert(server_object);
+ }
+
+ void UnregisterServerObject(KAutoObject* server_object) {
+ std::scoped_lock lk(server_objects_lock);
+ server_objects.erase(server_object);
+ }
+
std::weak_ptr<Kernel::ServiceThread> CreateServiceThread(KernelCore& kernel,
const std::string& name) {
auto service_thread = std::make_shared<Kernel::ServiceThread>(kernel, 1, name);
@@ -677,6 +694,12 @@ struct KernelCore::Impl {
void ReleaseServiceThread(std::weak_ptr<Kernel::ServiceThread> service_thread) {
if (auto strong_ptr = service_thread.lock()) {
+ if (strong_ptr == default_service_thread.lock()) {
+ // Nothing to do here, the service is using default_service_thread, which will be
+ // released on shutdown.
+ return;
+ }
+
service_threads_manager.QueueWork(
[this, strong_ptr{std::move(strong_ptr)}]() { service_threads.erase(strong_ptr); });
}
@@ -686,7 +709,7 @@ struct KernelCore::Impl {
service_threads_manager.QueueWork([this]() { service_threads.clear(); });
}
- std::mutex server_ports_lock;
+ std::mutex server_objects_lock;
std::mutex registered_objects_lock;
std::mutex registered_in_use_objects_lock;
@@ -697,7 +720,7 @@ struct KernelCore::Impl {
// Lists all processes that exist in the current session.
std::vector<KProcess*> process_list;
- KProcess* current_process{};
+ std::atomic<KProcess*> current_process{};
std::unique_ptr<Kernel::GlobalSchedulerContext> global_scheduler_context;
Kernel::TimeManager time_manager;
@@ -716,7 +739,7 @@ struct KernelCore::Impl {
/// the ConnectToPort SVC.
std::unordered_map<std::string, ServiceInterfaceFactory> service_interface_factory;
NamedPortTable named_ports;
- std::unordered_set<KServerPort*> server_ports;
+ std::unordered_set<KAutoObject*> server_objects;
std::unordered_set<KAutoObject*> registered_objects;
std::unordered_set<KAutoObject*> registered_in_use_objects;
@@ -734,12 +757,14 @@ struct KernelCore::Impl {
Kernel::KSharedMemory* font_shared_mem{};
Kernel::KSharedMemory* irs_shared_mem{};
Kernel::KSharedMemory* time_shared_mem{};
+ Kernel::KSharedMemory* hidbus_shared_mem{};
// Memory layout
std::unique_ptr<KMemoryLayout> memory_layout;
// Threads used for services
- std::unordered_set<std::shared_ptr<Kernel::ServiceThread>> service_threads;
+ std::unordered_set<std::shared_ptr<ServiceThread>> service_threads;
+ std::weak_ptr<ServiceThread> default_service_thread;
Common::ThreadWorker service_threads_manager;
std::array<KThread*, Core::Hardware::NUM_CPU_CORES> suspend_threads;
@@ -920,23 +945,31 @@ KClientPort* KernelCore::CreateNamedServicePort(std::string name) {
return impl->CreateNamedServicePort(std::move(name));
}
+void KernelCore::RegisterServerObject(KAutoObject* server_object) {
+ impl->RegisterServerObject(server_object);
+}
+
+void KernelCore::UnregisterServerObject(KAutoObject* server_object) {
+ impl->UnregisterServerObject(server_object);
+}
+
void KernelCore::RegisterKernelObject(KAutoObject* object) {
- std::lock_guard lk(impl->registered_objects_lock);
+ std::scoped_lock lk{impl->registered_objects_lock};
impl->registered_objects.insert(object);
}
void KernelCore::UnregisterKernelObject(KAutoObject* object) {
- std::lock_guard lk(impl->registered_objects_lock);
+ std::scoped_lock lk{impl->registered_objects_lock};
impl->registered_objects.erase(object);
}
void KernelCore::RegisterInUseObject(KAutoObject* object) {
- std::lock_guard lk(impl->registered_in_use_objects_lock);
+ std::scoped_lock lk{impl->registered_in_use_objects_lock};
impl->registered_in_use_objects.insert(object);
}
void KernelCore::UnregisterInUseObject(KAutoObject* object) {
- std::lock_guard lk(impl->registered_in_use_objects_lock);
+ std::scoped_lock lk{impl->registered_in_use_objects_lock};
impl->registered_in_use_objects.erase(object);
}
@@ -1024,6 +1057,14 @@ const Kernel::KSharedMemory& KernelCore::GetTimeSharedMem() const {
return *impl->time_shared_mem;
}
+Kernel::KSharedMemory& KernelCore::GetHidBusSharedMem() {
+ return *impl->hidbus_shared_mem;
+}
+
+const Kernel::KSharedMemory& KernelCore::GetHidBusSharedMem() const {
+ return *impl->hidbus_shared_mem;
+}
+
void KernelCore::Suspend(bool in_suspention) {
const bool should_suspend = exception_exited || in_suspention;
{
@@ -1065,6 +1106,10 @@ std::weak_ptr<Kernel::ServiceThread> KernelCore::CreateServiceThread(const std::
return impl->CreateServiceThread(*this, name);
}
+std::weak_ptr<Kernel::ServiceThread> KernelCore::GetDefaultServiceThread() const {
+ return impl->default_service_thread;
+}
+
void KernelCore::ReleaseServiceThread(std::weak_ptr<Kernel::ServiceThread> service_thread) {
impl->ReleaseServiceThread(service_thread);
}
diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h
index 4c68e96df..12e44b8a5 100644
--- a/src/core/hle/kernel/kernel.h
+++ b/src/core/hle/kernel/kernel.h
@@ -195,6 +195,14 @@ public:
/// Opens a port to a service previously registered with RegisterNamedService.
KClientPort* CreateNamedServicePort(std::string name);
+ /// Registers a server session or port with the gobal emulation state, to be freed on shutdown.
+ /// This is necessary because we do not emulate processes for HLE sessions and ports.
+ void RegisterServerObject(KAutoObject* server_object);
+
+ /// Unregisters a server session or port previously registered with RegisterServerSession when
+ /// it was destroyed during the current emulation session.
+ void UnregisterServerObject(KAutoObject* server_object);
+
/// Registers all kernel objects with the global emulation state, this is purely for tracking
/// leaks after emulation has been shutdown.
void RegisterKernelObject(KAutoObject* object);
@@ -256,6 +264,12 @@ public:
/// Gets the shared memory object for Time services.
const Kernel::KSharedMemory& GetTimeSharedMem() const;
+ /// Gets the shared memory object for HIDBus services.
+ Kernel::KSharedMemory& GetHidBusSharedMem();
+
+ /// Gets the shared memory object for HIDBus services.
+ const Kernel::KSharedMemory& GetHidBusSharedMem() const;
+
/// Suspend/unsuspend the OS.
void Suspend(bool in_suspention);
@@ -271,9 +285,11 @@ public:
void ExitSVCProfile();
/**
- * Creates an HLE service thread, which are used to execute service routines asynchronously.
- * While these are allocated per ServerSession, these need to be owned and managed outside
- * of ServerSession to avoid a circular dependency.
+ * Creates a host thread to execute HLE service requests, which are used to execute service
+ * routines asynchronously. While these are allocated per ServerSession, these need to be owned
+ * and managed outside of ServerSession to avoid a circular dependency. In general, most
+ * services can just use the default service thread, and not need their own host service thread.
+ * See GetDefaultServiceThread.
* @param name String name for the ServerSession creating this thread, used for debug
* purposes.
* @returns The a weak pointer newly created service thread.
@@ -281,6 +297,14 @@ public:
std::weak_ptr<Kernel::ServiceThread> CreateServiceThread(const std::string& name);
/**
+ * Gets the default host service thread, which executes HLE service requests. Unless service
+ * requests need to block on the host, the default service thread should be used in favor of
+ * creating a new service thread.
+ * @returns The a weak pointer for the default service thread.
+ */
+ std::weak_ptr<Kernel::ServiceThread> GetDefaultServiceThread() const;
+
+ /**
* Releases a HLE service thread, instructing KernelCore to free it. This should be called when
* the ServerSession associated with the thread is destroyed.
* @param service_thread Service thread to release.
diff --git a/src/core/hle/kernel/physical_core.cpp b/src/core/hle/kernel/physical_core.cpp
index 7477668e4..cc49e8c7e 100644
--- a/src/core/hle/kernel/physical_core.cpp
+++ b/src/core/hle/kernel/physical_core.cpp
@@ -2,7 +2,6 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include "common/spin_lock.h"
#include "core/arm/cpu_interrupt_handler.h"
#include "core/arm/dynarmic/arm_dynarmic_32.h"
#include "core/arm/dynarmic/arm_dynarmic_64.h"
@@ -16,7 +15,7 @@ namespace Kernel {
PhysicalCore::PhysicalCore(std::size_t core_index_, Core::System& system_, KScheduler& scheduler_,
Core::CPUInterrupts& interrupts_)
: core_index{core_index_}, system{system_}, scheduler{scheduler_},
- interrupts{interrupts_}, guard{std::make_unique<Common::SpinLock>()} {
+ interrupts{interrupts_}, guard{std::make_unique<std::mutex>()} {
#ifdef ARCHITECTURE_x86_64
// TODO(bunnei): Initialization relies on a core being available. We may later replace this with
// a 32-bit instance of Dynarmic. This should be abstracted out to a CPU manager.
@@ -58,6 +57,7 @@ bool PhysicalCore::IsInterrupted() const {
void PhysicalCore::Interrupt() {
guard->lock();
interrupts[core_index].SetInterrupt(true);
+ arm_interface->SignalInterrupt();
guard->unlock();
}
diff --git a/src/core/hle/kernel/physical_core.h b/src/core/hle/kernel/physical_core.h
index 16a032e89..f2112fc1d 100644
--- a/src/core/hle/kernel/physical_core.h
+++ b/src/core/hle/kernel/physical_core.h
@@ -6,13 +6,10 @@
#include <cstddef>
#include <memory>
+#include <mutex>
#include "core/arm/arm_interface.h"
-namespace Common {
-class SpinLock;
-}
-
namespace Kernel {
class KScheduler;
} // namespace Kernel
@@ -91,7 +88,7 @@ private:
Core::System& system;
Kernel::KScheduler& scheduler;
Core::CPUInterrupts& interrupts;
- std::unique_ptr<Common::SpinLock> guard;
+ std::unique_ptr<std::mutex> guard;
std::unique_ptr<Core::ARM_Interface> arm_interface;
};
diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp
index 839171e85..0c86435b5 100644
--- a/src/core/hle/kernel/svc.cpp
+++ b/src/core/hle/kernel/svc.cpp
@@ -1362,8 +1362,11 @@ static ResultCode MapProcessMemory(Core::System& system, VAddr dst_address, Hand
ResultInvalidMemoryRegion);
// Create a new page group.
- KMemoryInfo kBlockInfo = dst_pt.QueryInfo(dst_address);
- KPageLinkedList pg(kBlockInfo.GetAddress(), kBlockInfo.GetNumPages());
+ KPageLinkedList pg;
+ R_TRY(src_pt.MakeAndOpenPageGroup(
+ std::addressof(pg), src_address, size / PageSize, KMemoryState::FlagCanMapProcess,
+ KMemoryState::FlagCanMapProcess, KMemoryPermission::None, KMemoryPermission::None,
+ KMemoryAttribute::All, KMemoryAttribute::None));
// Map the group.
R_TRY(dst_pt.MapPages(dst_address, pg, KMemoryState::SharedCode,
@@ -1408,8 +1411,8 @@ static ResultCode UnmapProcessMemory(Core::System& system, VAddr dst_address, Ha
}
static ResultCode CreateCodeMemory(Core::System& system, Handle* out, VAddr address, size_t size) {
- LOG_TRACE(Kernel_SVC, "called, handle_out={}, address=0x{:X}, size=0x{:X}",
- static_cast<void*>(out), address, size);
+ LOG_TRACE(Kernel_SVC, "called, address=0x{:X}, size=0x{:X}", address, size);
+
// Get kernel instance.
auto& kernel = system.Kernel();
@@ -1664,7 +1667,7 @@ static ResultCode UnmapProcessCodeMemory(Core::System& system, Handle process_ha
return ResultInvalidAddress;
}
- if (size == 0 || Common::Is4KBAligned(size)) {
+ if (size == 0 || !Common::Is4KBAligned(size)) {
LOG_ERROR(Kernel_SVC, "Size is zero or not page-aligned (size=0x{:016X}).", size);
return ResultInvalidSize;
}
@@ -1710,7 +1713,8 @@ static ResultCode UnmapProcessCodeMemory(Core::System& system, Handle process_ha
return ResultInvalidMemoryRegion;
}
- return page_table.UnmapCodeMemory(dst_address, src_address, size);
+ return page_table.UnmapCodeMemory(dst_address, src_address, size,
+ KPageTable::ICacheInvalidationStrategy::InvalidateAll);
}
/// Exits the current process
diff --git a/src/core/hle/kernel/time_manager.cpp b/src/core/hle/kernel/time_manager.cpp
index aa985d820..5b8fe8eae 100644
--- a/src/core/hle/kernel/time_manager.cpp
+++ b/src/core/hle/kernel/time_manager.cpp
@@ -24,7 +24,7 @@ TimeManager::TimeManager(Core::System& system_) : system{system_} {
}
void TimeManager::ScheduleTimeEvent(KThread* thread, s64 nanoseconds) {
- std::lock_guard lock{mutex};
+ std::scoped_lock lock{mutex};
if (nanoseconds > 0) {
ASSERT(thread);
ASSERT(thread->GetState() != ThreadState::Runnable);
@@ -35,7 +35,7 @@ void TimeManager::ScheduleTimeEvent(KThread* thread, s64 nanoseconds) {
}
void TimeManager::UnscheduleTimeEvent(KThread* thread) {
- std::lock_guard lock{mutex};
+ std::scoped_lock lock{mutex};
system.CoreTiming().UnscheduleEvent(time_manager_event_type,
reinterpret_cast<uintptr_t>(thread));
}
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index 420de3c54..4d7e5ecd3 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -1337,7 +1337,7 @@ IApplicationFunctions::IApplicationFunctions(Core::System& system_)
{200, nullptr, "GetLastApplicationExitReason"},
{500, nullptr, "StartContinuousRecordingFlushForDebug"},
{1000, nullptr, "CreateMovieMaker"},
- {1001, nullptr, "PrepareForJit"},
+ {1001, &IApplicationFunctions::PrepareForJit, "PrepareForJit"},
};
// clang-format on
@@ -1787,6 +1787,13 @@ void IApplicationFunctions::GetHealthWarningDisappearedSystemEvent(Kernel::HLERe
rb.PushCopyObjects(health_warning_disappeared_system_event->GetReadableEvent());
}
+void IApplicationFunctions::PrepareForJit(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
void InstallInterfaces(SM::ServiceManager& service_manager, NVFlinger::NVFlinger& nvflinger,
Core::System& system) {
auto message_queue = std::make_shared<AppletMessageQueue>(system);
diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h
index fdd937b82..11a3c0459 100644
--- a/src/core/hle/service/am/am.h
+++ b/src/core/hle/service/am/am.h
@@ -336,6 +336,7 @@ private:
void TryPopFromFriendInvitationStorageChannel(Kernel::HLERequestContext& ctx);
void GetNotificationStorageChannelEvent(Kernel::HLERequestContext& ctx);
void GetHealthWarningDisappearedSystemEvent(Kernel::HLERequestContext& ctx);
+ void PrepareForJit(Kernel::HLERequestContext& ctx);
KernelHelpers::ServiceContext service_context;
diff --git a/src/core/hle/service/am/applets/applet_mii.cpp b/src/core/hle/service/am/applets/applet_mii.cpp
deleted file mode 100644
index facede97e..000000000
--- a/src/core/hle/service/am/applets/applet_mii.cpp
+++ /dev/null
@@ -1,100 +0,0 @@
-// Copyright 2022 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include "common/assert.h"
-#include "common/logging/log.h"
-#include "core/core.h"
-#include "core/frontend/applets/mii.h"
-#include "core/hle/service/am/am.h"
-#include "core/hle/service/am/applets/applet_mii.h"
-
-namespace Service::AM::Applets {
-
-Mii::Mii(Core::System& system_, LibraryAppletMode applet_mode_,
- const Core::Frontend::MiiApplet& frontend_)
- : Applet{system_, applet_mode_}, frontend{frontend_}, system{system_} {}
-
-Mii::~Mii() = default;
-
-void Mii::Initialize() {
- is_complete = false;
-
- const auto storage = broker.PopNormalDataToApplet();
- ASSERT(storage != nullptr);
-
- const auto data = storage->GetData();
- ASSERT(data.size() == sizeof(MiiAppletInput));
-
- std::memcpy(&input_data, data.data(), sizeof(MiiAppletInput));
-}
-
-bool Mii::TransactionComplete() const {
- return is_complete;
-}
-
-ResultCode Mii::GetStatus() const {
- return ResultSuccess;
-}
-
-void Mii::ExecuteInteractive() {
- UNREACHABLE_MSG("Unexpected interactive applet data!");
-}
-
-void Mii::Execute() {
- if (is_complete) {
- return;
- }
-
- const auto callback = [this](const Core::Frontend::MiiParameters& parameters) {
- DisplayCompleted(parameters);
- };
-
- switch (input_data.applet_mode) {
- case MiiAppletMode::ShowMiiEdit: {
- Service::Mii::MiiManager manager;
- Core::Frontend::MiiParameters params{
- .is_editable = false,
- .mii_data = input_data.mii_char_info.mii_data,
- };
- frontend.ShowMii(params, callback);
- break;
- }
- case MiiAppletMode::EditMii: {
- Service::Mii::MiiManager manager;
- Core::Frontend::MiiParameters params{
- .is_editable = true,
- .mii_data = input_data.mii_char_info.mii_data,
- };
- frontend.ShowMii(params, callback);
- break;
- }
- case MiiAppletMode::CreateMii: {
- Service::Mii::MiiManager manager;
- Core::Frontend::MiiParameters params{
- .is_editable = true,
- .mii_data = manager.BuildDefault(0),
- };
- frontend.ShowMii(params, callback);
- break;
- }
- default:
- UNIMPLEMENTED_MSG("Unimplemented LibAppletMiiEdit mode={:02X}!", input_data.applet_mode);
- }
-}
-
-void Mii::DisplayCompleted(const Core::Frontend::MiiParameters& parameters) {
- is_complete = true;
-
- std::vector<u8> reply(sizeof(AppletOutputForCharInfoEditing));
- output_data = {
- .result = ResultSuccess,
- .mii_data = parameters.mii_data,
- };
-
- std::memcpy(reply.data(), &output_data, sizeof(AppletOutputForCharInfoEditing));
- broker.PushNormalDataFromApplet(std::make_shared<IStorage>(system, std::move(reply)));
- broker.SignalStateChanged();
-}
-
-} // namespace Service::AM::Applets
diff --git a/src/core/hle/service/am/applets/applet_mii.h b/src/core/hle/service/am/applets/applet_mii.h
deleted file mode 100644
index 42326bfc2..000000000
--- a/src/core/hle/service/am/applets/applet_mii.h
+++ /dev/null
@@ -1,90 +0,0 @@
-// Copyright 2022 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <array>
-
-#include "core/hle/result.h"
-#include "core/hle/service/am/applets/applets.h"
-#include "core/hle/service/mii/mii_manager.h"
-
-namespace Core {
-class System;
-}
-
-namespace Service::AM::Applets {
-
-// This is nn::mii::AppletMode
-enum class MiiAppletMode : u32 {
- ShowMiiEdit = 0,
- AppendMii = 1,
- AppendMiiImage = 2,
- UpdateMiiImage = 3,
- CreateMii = 4,
- EditMii = 5,
-};
-
-struct MiiCharInfo {
- Service::Mii::MiiInfo mii_data{};
- INSERT_PADDING_BYTES(0x28);
-};
-static_assert(sizeof(MiiCharInfo) == 0x80, "MiiCharInfo has incorrect size.");
-
-// This is nn::mii::AppletInput
-struct MiiAppletInput {
- s32 version{};
- MiiAppletMode applet_mode{};
- u32 special_mii_key_code{};
- union {
- std::array<Common::UUID, 8> valid_uuid;
- MiiCharInfo mii_char_info;
- };
- Common::UUID used_uuid;
- INSERT_PADDING_BYTES(0x64);
-};
-static_assert(sizeof(MiiAppletInput) == 0x100, "MiiAppletInput has incorrect size.");
-
-// This is nn::mii::AppletOutput
-struct MiiAppletOutput {
- ResultCode result{ResultSuccess};
- s32 index{};
- INSERT_PADDING_BYTES(0x18);
-};
-static_assert(sizeof(MiiAppletOutput) == 0x20, "MiiAppletOutput has incorrect size.");
-
-// This is nn::mii::AppletOutputForCharInfoEditing
-struct AppletOutputForCharInfoEditing {
- ResultCode result{ResultSuccess};
- Service::Mii::MiiInfo mii_data{};
- INSERT_PADDING_BYTES(0x24);
-};
-static_assert(sizeof(AppletOutputForCharInfoEditing) == 0x80,
- "AppletOutputForCharInfoEditing has incorrect size.");
-
-class Mii final : public Applet {
-public:
- explicit Mii(Core::System& system_, LibraryAppletMode applet_mode_,
- const Core::Frontend::MiiApplet& frontend_);
- ~Mii() override;
-
- void Initialize() override;
-
- bool TransactionComplete() const override;
- ResultCode GetStatus() const override;
- void ExecuteInteractive() override;
- void Execute() override;
-
- void DisplayCompleted(const Core::Frontend::MiiParameters& parameters);
-
-private:
- const Core::Frontend::MiiApplet& frontend;
- MiiAppletInput input_data{};
- AppletOutputForCharInfoEditing output_data{};
-
- bool is_complete = false;
- Core::System& system;
-};
-
-} // namespace Service::AM::Applets
diff --git a/src/core/hle/service/am/applets/applet_mii_edit.cpp b/src/core/hle/service/am/applets/applet_mii_edit.cpp
new file mode 100644
index 000000000..8fbde1be4
--- /dev/null
+++ b/src/core/hle/service/am/applets/applet_mii_edit.cpp
@@ -0,0 +1,139 @@
+// Copyright 2022 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/assert.h"
+#include "common/logging/log.h"
+#include "core/core.h"
+#include "core/frontend/applets/mii_edit.h"
+#include "core/hle/service/am/am.h"
+#include "core/hle/service/am/applets/applet_mii_edit.h"
+#include "core/hle/service/mii/mii_manager.h"
+
+namespace Service::AM::Applets {
+
+MiiEdit::MiiEdit(Core::System& system_, LibraryAppletMode applet_mode_,
+ const Core::Frontend::MiiEditApplet& frontend_)
+ : Applet{system_, applet_mode_}, frontend{frontend_}, system{system_} {}
+
+MiiEdit::~MiiEdit() = default;
+
+void MiiEdit::Initialize() {
+ // Note: MiiEdit is not initialized with common arguments.
+ // Instead, it is initialized by an AppletInput storage with size 0x100 bytes.
+ // Do NOT call Applet::Initialize() here.
+
+ const auto storage = broker.PopNormalDataToApplet();
+ ASSERT(storage != nullptr);
+
+ const auto applet_input_data = storage->GetData();
+ ASSERT(applet_input_data.size() >= sizeof(MiiEditAppletInputCommon));
+
+ std::memcpy(&applet_input_common, applet_input_data.data(), sizeof(MiiEditAppletInputCommon));
+
+ LOG_INFO(Service_AM,
+ "Initializing MiiEdit Applet with MiiEditAppletVersion={} and MiiEditAppletMode={}",
+ applet_input_common.version, applet_input_common.applet_mode);
+
+ switch (applet_input_common.version) {
+ case MiiEditAppletVersion::Version3:
+ ASSERT(applet_input_data.size() ==
+ sizeof(MiiEditAppletInputCommon) + sizeof(MiiEditAppletInputV3));
+ std::memcpy(&applet_input_v3, applet_input_data.data() + sizeof(MiiEditAppletInputCommon),
+ sizeof(MiiEditAppletInputV3));
+ break;
+ case MiiEditAppletVersion::Version4:
+ ASSERT(applet_input_data.size() ==
+ sizeof(MiiEditAppletInputCommon) + sizeof(MiiEditAppletInputV4));
+ std::memcpy(&applet_input_v4, applet_input_data.data() + sizeof(MiiEditAppletInputCommon),
+ sizeof(MiiEditAppletInputV4));
+ break;
+ default:
+ UNIMPLEMENTED_MSG("Unknown MiiEditAppletVersion={} with size={}",
+ applet_input_common.version, applet_input_data.size());
+ ASSERT(applet_input_data.size() >=
+ sizeof(MiiEditAppletInputCommon) + sizeof(MiiEditAppletInputV4));
+ std::memcpy(&applet_input_v4, applet_input_data.data() + sizeof(MiiEditAppletInputCommon),
+ sizeof(MiiEditAppletInputV4));
+ break;
+ }
+}
+
+bool MiiEdit::TransactionComplete() const {
+ return is_complete;
+}
+
+ResultCode MiiEdit::GetStatus() const {
+ return ResultSuccess;
+}
+
+void MiiEdit::ExecuteInteractive() {
+ UNREACHABLE_MSG("Attempted to call interactive execution on non-interactive applet.");
+}
+
+void MiiEdit::Execute() {
+ if (is_complete) {
+ return;
+ }
+
+ // This is a default stub for each of the MiiEdit applet modes.
+ switch (applet_input_common.applet_mode) {
+ case MiiEditAppletMode::ShowMiiEdit:
+ case MiiEditAppletMode::AppendMii:
+ case MiiEditAppletMode::AppendMiiImage:
+ case MiiEditAppletMode::UpdateMiiImage:
+ MiiEditOutput(MiiEditResult::Success, 0);
+ break;
+ case MiiEditAppletMode::CreateMii:
+ case MiiEditAppletMode::EditMii: {
+ Service::Mii::MiiManager mii_manager;
+
+ const MiiEditCharInfo char_info{
+ .mii_info{applet_input_common.applet_mode == MiiEditAppletMode::EditMii
+ ? applet_input_v4.char_info.mii_info
+ : mii_manager.BuildDefault(0)},
+ };
+
+ MiiEditOutputForCharInfoEditing(MiiEditResult::Success, char_info);
+ break;
+ }
+ default:
+ UNIMPLEMENTED_MSG("Unknown MiiEditAppletMode={}", applet_input_common.applet_mode);
+
+ MiiEditOutput(MiiEditResult::Success, 0);
+ break;
+ }
+}
+
+void MiiEdit::MiiEditOutput(MiiEditResult result, s32 index) {
+ const MiiEditAppletOutput applet_output{
+ .result{result},
+ .index{index},
+ };
+
+ std::vector<u8> out_data(sizeof(MiiEditAppletOutput));
+ std::memcpy(out_data.data(), &applet_output, sizeof(MiiEditAppletOutput));
+
+ is_complete = true;
+
+ broker.PushNormalDataFromApplet(std::make_shared<IStorage>(system, std::move(out_data)));
+ broker.SignalStateChanged();
+}
+
+void MiiEdit::MiiEditOutputForCharInfoEditing(MiiEditResult result,
+ const MiiEditCharInfo& char_info) {
+ const MiiEditAppletOutputForCharInfoEditing applet_output{
+ .result{result},
+ .char_info{char_info},
+ };
+
+ std::vector<u8> out_data(sizeof(MiiEditAppletOutputForCharInfoEditing));
+ std::memcpy(out_data.data(), &applet_output, sizeof(MiiEditAppletOutputForCharInfoEditing));
+
+ is_complete = true;
+
+ broker.PushNormalDataFromApplet(std::make_shared<IStorage>(system, std::move(out_data)));
+ broker.SignalStateChanged();
+}
+
+} // namespace Service::AM::Applets
diff --git a/src/core/hle/service/am/applets/applet_mii_edit.h b/src/core/hle/service/am/applets/applet_mii_edit.h
new file mode 100644
index 000000000..e9ca0e2af
--- /dev/null
+++ b/src/core/hle/service/am/applets/applet_mii_edit.h
@@ -0,0 +1,45 @@
+// Copyright 2022 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/result.h"
+#include "core/hle/service/am/applets/applet_mii_edit_types.h"
+#include "core/hle/service/am/applets/applets.h"
+
+namespace Core {
+class System;
+} // namespace Core
+
+namespace Service::AM::Applets {
+
+class MiiEdit final : public Applet {
+public:
+ explicit MiiEdit(Core::System& system_, LibraryAppletMode applet_mode_,
+ const Core::Frontend::MiiEditApplet& frontend_);
+ ~MiiEdit() override;
+
+ void Initialize() override;
+
+ bool TransactionComplete() const override;
+ ResultCode GetStatus() const override;
+ void ExecuteInteractive() override;
+ void Execute() override;
+
+ void MiiEditOutput(MiiEditResult result, s32 index);
+
+ void MiiEditOutputForCharInfoEditing(MiiEditResult result, const MiiEditCharInfo& char_info);
+
+private:
+ const Core::Frontend::MiiEditApplet& frontend;
+ Core::System& system;
+
+ MiiEditAppletInputCommon applet_input_common{};
+ MiiEditAppletInputV3 applet_input_v3{};
+ MiiEditAppletInputV4 applet_input_v4{};
+
+ bool is_complete{false};
+};
+
+} // namespace Service::AM::Applets
diff --git a/src/core/hle/service/am/applets/applet_mii_edit_types.h b/src/core/hle/service/am/applets/applet_mii_edit_types.h
new file mode 100644
index 000000000..70dea0007
--- /dev/null
+++ b/src/core/hle/service/am/applets/applet_mii_edit_types.h
@@ -0,0 +1,83 @@
+// Copyright 2022 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "core/hle/service/mii/types.h"
+
+namespace Service::AM::Applets {
+
+enum class MiiEditAppletVersion : s32 {
+ Version3 = 0x3, // 1.0.0 - 10.1.1
+ Version4 = 0x4, // 10.2.0+
+};
+
+// This is nn::mii::AppletMode
+enum class MiiEditAppletMode : u32 {
+ ShowMiiEdit = 0,
+ AppendMii = 1,
+ AppendMiiImage = 2,
+ UpdateMiiImage = 3,
+ CreateMii = 4,
+ EditMii = 5,
+};
+
+enum class MiiEditResult : u32 {
+ Success,
+ Cancel,
+};
+
+struct MiiEditCharInfo {
+ Service::Mii::MiiInfo mii_info{};
+};
+static_assert(sizeof(MiiEditCharInfo) == 0x58, "MiiEditCharInfo has incorrect size.");
+
+struct MiiEditAppletInputCommon {
+ MiiEditAppletVersion version{};
+ MiiEditAppletMode applet_mode{};
+};
+static_assert(sizeof(MiiEditAppletInputCommon) == 0x8,
+ "MiiEditAppletInputCommon has incorrect size.");
+
+struct MiiEditAppletInputV3 {
+ u32 special_mii_key_code{};
+ std::array<Common::UUID, 8> valid_uuids{};
+ Common::UUID used_uuid{};
+ INSERT_PADDING_BYTES(0x64);
+};
+static_assert(sizeof(MiiEditAppletInputV3) == 0x100 - sizeof(MiiEditAppletInputCommon),
+ "MiiEditAppletInputV3 has incorrect size.");
+
+struct MiiEditAppletInputV4 {
+ u32 special_mii_key_code{};
+ MiiEditCharInfo char_info{};
+ INSERT_PADDING_BYTES(0x28);
+ Common::UUID used_uuid{};
+ INSERT_PADDING_BYTES(0x64);
+};
+static_assert(sizeof(MiiEditAppletInputV4) == 0x100 - sizeof(MiiEditAppletInputCommon),
+ "MiiEditAppletInputV4 has incorrect size.");
+
+// This is nn::mii::AppletOutput
+struct MiiEditAppletOutput {
+ MiiEditResult result{};
+ s32 index{};
+ INSERT_PADDING_BYTES(0x18);
+};
+static_assert(sizeof(MiiEditAppletOutput) == 0x20, "MiiEditAppletOutput has incorrect size.");
+
+// This is nn::mii::AppletOutputForCharInfoEditing
+struct MiiEditAppletOutputForCharInfoEditing {
+ MiiEditResult result{};
+ MiiEditCharInfo char_info{};
+ INSERT_PADDING_BYTES(0x24);
+};
+static_assert(sizeof(MiiEditAppletOutputForCharInfoEditing) == 0x80,
+ "MiiEditAppletOutputForCharInfoEditing has incorrect size.");
+
+} // namespace Service::AM::Applets
diff --git a/src/core/hle/service/am/applets/applet_software_keyboard.cpp b/src/core/hle/service/am/applets/applet_software_keyboard.cpp
index f38f53f69..ee669686c 100644
--- a/src/core/hle/service/am/applets/applet_software_keyboard.cpp
+++ b/src/core/hle/service/am/applets/applet_software_keyboard.cpp
@@ -226,7 +226,7 @@ void SoftwareKeyboard::InitializeForeground() {
ASSERT(work_buffer_storage != nullptr);
if (swkbd_config_common.initial_string_length == 0) {
- InitializeFrontendKeyboard();
+ InitializeFrontendNormalKeyboard();
return;
}
@@ -243,7 +243,7 @@ void SoftwareKeyboard::InitializeForeground() {
LOG_DEBUG(Service_AM, "\nInitial Text: {}", Common::UTF16ToUTF8(initial_text));
- InitializeFrontendKeyboard();
+ InitializeFrontendNormalKeyboard();
}
void SoftwareKeyboard::InitializeBackground(LibraryAppletMode library_applet_mode) {
@@ -480,129 +480,173 @@ void SoftwareKeyboard::ChangeState(SwkbdState state) {
ReplyDefault();
}
-void SoftwareKeyboard::InitializeFrontendKeyboard() {
- if (is_background) {
- const auto& appear_arg = swkbd_calc_arg.appear_arg;
-
- std::u16string ok_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(
- appear_arg.ok_text.data(), appear_arg.ok_text.size());
-
- const u32 max_text_length =
- appear_arg.max_text_length > 0 && appear_arg.max_text_length <= DEFAULT_MAX_TEXT_LENGTH
- ? appear_arg.max_text_length
- : DEFAULT_MAX_TEXT_LENGTH;
-
- const u32 min_text_length =
- appear_arg.min_text_length <= max_text_length ? appear_arg.min_text_length : 0;
-
- const s32 initial_cursor_position =
- current_cursor_position > 0 ? current_cursor_position : 0;
-
- const auto text_draw_type =
- max_text_length <= 32 ? SwkbdTextDrawType::Line : SwkbdTextDrawType::Box;
-
- Core::Frontend::KeyboardInitializeParameters initialize_parameters{
- .ok_text{std::move(ok_text)},
- .header_text{},
- .sub_text{},
- .guide_text{},
- .initial_text{current_text},
- .max_text_length{max_text_length},
- .min_text_length{min_text_length},
- .initial_cursor_position{initial_cursor_position},
- .type{appear_arg.type},
- .password_mode{SwkbdPasswordMode::Disabled},
- .text_draw_type{text_draw_type},
- .key_disable_flags{appear_arg.key_disable_flags},
- .use_blur_background{false},
- .enable_backspace_button{swkbd_calc_arg.enable_backspace_button},
- .enable_return_button{appear_arg.enable_return_button},
- .disable_cancel_button{appear_arg.disable_cancel_button},
- };
-
- frontend.InitializeKeyboard(
- true, std::move(initialize_parameters), {},
- [this](SwkbdReplyType reply_type, std::u16string submitted_text, s32 cursor_position) {
- SubmitTextInline(reply_type, submitted_text, cursor_position);
- });
- } else {
- std::u16string ok_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(
- swkbd_config_common.ok_text.data(), swkbd_config_common.ok_text.size());
-
- std::u16string header_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(
- swkbd_config_common.header_text.data(), swkbd_config_common.header_text.size());
-
- std::u16string sub_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(
- swkbd_config_common.sub_text.data(), swkbd_config_common.sub_text.size());
-
- std::u16string guide_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(
- swkbd_config_common.guide_text.data(), swkbd_config_common.guide_text.size());
-
- const u32 max_text_length =
- swkbd_config_common.max_text_length > 0 &&
- swkbd_config_common.max_text_length <= DEFAULT_MAX_TEXT_LENGTH
- ? swkbd_config_common.max_text_length
- : DEFAULT_MAX_TEXT_LENGTH;
-
- const u32 min_text_length = swkbd_config_common.min_text_length <= max_text_length
- ? swkbd_config_common.min_text_length
- : 0;
-
- const s32 initial_cursor_position = [this] {
- switch (swkbd_config_common.initial_cursor_position) {
- case SwkbdInitialCursorPosition::Start:
- default:
- return 0;
- case SwkbdInitialCursorPosition::End:
- return static_cast<s32>(initial_text.size());
- }
- }();
-
- const auto text_draw_type = [this, max_text_length] {
- switch (swkbd_config_common.text_draw_type) {
- case SwkbdTextDrawType::Line:
- default:
- return max_text_length <= 32 ? SwkbdTextDrawType::Line : SwkbdTextDrawType::Box;
- case SwkbdTextDrawType::Box:
- case SwkbdTextDrawType::DownloadCode:
- return swkbd_config_common.text_draw_type;
- }
- }();
-
- const auto enable_return_button = text_draw_type == SwkbdTextDrawType::Box
- ? swkbd_config_common.enable_return_button
- : false;
-
- const auto disable_cancel_button = swkbd_applet_version >= SwkbdAppletVersion::Version393227
- ? swkbd_config_new.disable_cancel_button
- : false;
-
- Core::Frontend::KeyboardInitializeParameters initialize_parameters{
- .ok_text{std::move(ok_text)},
- .header_text{std::move(header_text)},
- .sub_text{std::move(sub_text)},
- .guide_text{std::move(guide_text)},
- .initial_text{initial_text},
- .max_text_length{max_text_length},
- .min_text_length{min_text_length},
- .initial_cursor_position{initial_cursor_position},
- .type{swkbd_config_common.type},
- .password_mode{swkbd_config_common.password_mode},
- .text_draw_type{text_draw_type},
- .key_disable_flags{swkbd_config_common.key_disable_flags},
- .use_blur_background{swkbd_config_common.use_blur_background},
- .enable_backspace_button{true},
- .enable_return_button{enable_return_button},
- .disable_cancel_button{disable_cancel_button},
- };
-
- frontend.InitializeKeyboard(
- false, std::move(initialize_parameters),
- [this](SwkbdResult result, std::u16string submitted_text, bool confirmed) {
- SubmitTextNormal(result, submitted_text, confirmed);
- },
- {});
- }
+void SoftwareKeyboard::InitializeFrontendNormalKeyboard() {
+ std::u16string ok_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(
+ swkbd_config_common.ok_text.data(), swkbd_config_common.ok_text.size());
+
+ std::u16string header_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(
+ swkbd_config_common.header_text.data(), swkbd_config_common.header_text.size());
+
+ std::u16string sub_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(
+ swkbd_config_common.sub_text.data(), swkbd_config_common.sub_text.size());
+
+ std::u16string guide_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(
+ swkbd_config_common.guide_text.data(), swkbd_config_common.guide_text.size());
+
+ const u32 max_text_length =
+ swkbd_config_common.max_text_length > 0 &&
+ swkbd_config_common.max_text_length <= DEFAULT_MAX_TEXT_LENGTH
+ ? swkbd_config_common.max_text_length
+ : DEFAULT_MAX_TEXT_LENGTH;
+
+ const u32 min_text_length = swkbd_config_common.min_text_length <= max_text_length
+ ? swkbd_config_common.min_text_length
+ : 0;
+
+ const s32 initial_cursor_position = [this] {
+ switch (swkbd_config_common.initial_cursor_position) {
+ case SwkbdInitialCursorPosition::Start:
+ default:
+ return 0;
+ case SwkbdInitialCursorPosition::End:
+ return static_cast<s32>(initial_text.size());
+ }
+ }();
+
+ const auto text_draw_type = [this, max_text_length] {
+ switch (swkbd_config_common.text_draw_type) {
+ case SwkbdTextDrawType::Line:
+ default:
+ return max_text_length <= 32 ? SwkbdTextDrawType::Line : SwkbdTextDrawType::Box;
+ case SwkbdTextDrawType::Box:
+ case SwkbdTextDrawType::DownloadCode:
+ return swkbd_config_common.text_draw_type;
+ }
+ }();
+
+ const auto enable_return_button =
+ text_draw_type == SwkbdTextDrawType::Box ? swkbd_config_common.enable_return_button : false;
+
+ const auto disable_cancel_button = swkbd_applet_version >= SwkbdAppletVersion::Version393227
+ ? swkbd_config_new.disable_cancel_button
+ : false;
+
+ Core::Frontend::KeyboardInitializeParameters initialize_parameters{
+ .ok_text{std::move(ok_text)},
+ .header_text{std::move(header_text)},
+ .sub_text{std::move(sub_text)},
+ .guide_text{std::move(guide_text)},
+ .initial_text{initial_text},
+ .max_text_length{max_text_length},
+ .min_text_length{min_text_length},
+ .initial_cursor_position{initial_cursor_position},
+ .type{swkbd_config_common.type},
+ .password_mode{swkbd_config_common.password_mode},
+ .text_draw_type{text_draw_type},
+ .key_disable_flags{swkbd_config_common.key_disable_flags},
+ .use_blur_background{swkbd_config_common.use_blur_background},
+ .enable_backspace_button{true},
+ .enable_return_button{enable_return_button},
+ .disable_cancel_button{disable_cancel_button},
+ };
+
+ frontend.InitializeKeyboard(
+ false, std::move(initialize_parameters),
+ [this](SwkbdResult result, std::u16string submitted_text, bool confirmed) {
+ SubmitTextNormal(result, submitted_text, confirmed);
+ },
+ {});
+}
+
+void SoftwareKeyboard::InitializeFrontendInlineKeyboard(
+ Core::Frontend::KeyboardInitializeParameters initialize_parameters) {
+ frontend.InitializeKeyboard(
+ true, std::move(initialize_parameters), {},
+ [this](SwkbdReplyType reply_type, std::u16string submitted_text, s32 cursor_position) {
+ SubmitTextInline(reply_type, submitted_text, cursor_position);
+ });
+}
+
+void SoftwareKeyboard::InitializeFrontendInlineKeyboardOld() {
+ const auto& appear_arg = swkbd_calc_arg_old.appear_arg;
+
+ std::u16string ok_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(
+ appear_arg.ok_text.data(), appear_arg.ok_text.size());
+
+ const u32 max_text_length =
+ appear_arg.max_text_length > 0 && appear_arg.max_text_length <= DEFAULT_MAX_TEXT_LENGTH
+ ? appear_arg.max_text_length
+ : DEFAULT_MAX_TEXT_LENGTH;
+
+ const u32 min_text_length =
+ appear_arg.min_text_length <= max_text_length ? appear_arg.min_text_length : 0;
+
+ const s32 initial_cursor_position = current_cursor_position > 0 ? current_cursor_position : 0;
+
+ const auto text_draw_type =
+ max_text_length <= 32 ? SwkbdTextDrawType::Line : SwkbdTextDrawType::Box;
+
+ Core::Frontend::KeyboardInitializeParameters initialize_parameters{
+ .ok_text{std::move(ok_text)},
+ .header_text{},
+ .sub_text{},
+ .guide_text{},
+ .initial_text{current_text},
+ .max_text_length{max_text_length},
+ .min_text_length{min_text_length},
+ .initial_cursor_position{initial_cursor_position},
+ .type{appear_arg.type},
+ .password_mode{SwkbdPasswordMode::Disabled},
+ .text_draw_type{text_draw_type},
+ .key_disable_flags{appear_arg.key_disable_flags},
+ .use_blur_background{false},
+ .enable_backspace_button{swkbd_calc_arg_old.enable_backspace_button},
+ .enable_return_button{appear_arg.enable_return_button},
+ .disable_cancel_button{appear_arg.disable_cancel_button},
+ };
+
+ InitializeFrontendInlineKeyboard(std::move(initialize_parameters));
+}
+
+void SoftwareKeyboard::InitializeFrontendInlineKeyboardNew() {
+ const auto& appear_arg = swkbd_calc_arg_new.appear_arg;
+
+ std::u16string ok_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(
+ appear_arg.ok_text.data(), appear_arg.ok_text.size());
+
+ const u32 max_text_length =
+ appear_arg.max_text_length > 0 && appear_arg.max_text_length <= DEFAULT_MAX_TEXT_LENGTH
+ ? appear_arg.max_text_length
+ : DEFAULT_MAX_TEXT_LENGTH;
+
+ const u32 min_text_length =
+ appear_arg.min_text_length <= max_text_length ? appear_arg.min_text_length : 0;
+
+ const s32 initial_cursor_position = current_cursor_position > 0 ? current_cursor_position : 0;
+
+ const auto text_draw_type =
+ max_text_length <= 32 ? SwkbdTextDrawType::Line : SwkbdTextDrawType::Box;
+
+ Core::Frontend::KeyboardInitializeParameters initialize_parameters{
+ .ok_text{std::move(ok_text)},
+ .header_text{},
+ .sub_text{},
+ .guide_text{},
+ .initial_text{current_text},
+ .max_text_length{max_text_length},
+ .min_text_length{min_text_length},
+ .initial_cursor_position{initial_cursor_position},
+ .type{appear_arg.type},
+ .password_mode{SwkbdPasswordMode::Disabled},
+ .text_draw_type{text_draw_type},
+ .key_disable_flags{appear_arg.key_disable_flags},
+ .use_blur_background{false},
+ .enable_backspace_button{swkbd_calc_arg_new.enable_backspace_button},
+ .enable_return_button{appear_arg.enable_return_button},
+ .disable_cancel_button{appear_arg.disable_cancel_button},
+ };
+
+ InitializeFrontendInlineKeyboard(std::move(initialize_parameters));
}
void SoftwareKeyboard::ShowNormalKeyboard() {
@@ -614,14 +658,21 @@ void SoftwareKeyboard::ShowTextCheckDialog(SwkbdTextCheckResult text_check_resul
frontend.ShowTextCheckDialog(text_check_result, std::move(text_check_message));
}
-void SoftwareKeyboard::ShowInlineKeyboard() {
+void SoftwareKeyboard::ShowInlineKeyboard(
+ Core::Frontend::InlineAppearParameters appear_parameters) {
+ frontend.ShowInlineKeyboard(std::move(appear_parameters));
+
+ ChangeState(SwkbdState::InitializedIsShown);
+}
+
+void SoftwareKeyboard::ShowInlineKeyboardOld() {
if (swkbd_state != SwkbdState::InitializedIsHidden) {
return;
}
ChangeState(SwkbdState::InitializedIsAppearing);
- const auto& appear_arg = swkbd_calc_arg.appear_arg;
+ const auto& appear_arg = swkbd_calc_arg_old.appear_arg;
const u32 max_text_length =
appear_arg.max_text_length > 0 && appear_arg.max_text_length <= DEFAULT_MAX_TEXT_LENGTH
@@ -634,21 +685,54 @@ void SoftwareKeyboard::ShowInlineKeyboard() {
Core::Frontend::InlineAppearParameters appear_parameters{
.max_text_length{max_text_length},
.min_text_length{min_text_length},
- .key_top_scale_x{swkbd_calc_arg.key_top_scale_x},
- .key_top_scale_y{swkbd_calc_arg.key_top_scale_y},
- .key_top_translate_x{swkbd_calc_arg.key_top_translate_x},
- .key_top_translate_y{swkbd_calc_arg.key_top_translate_y},
+ .key_top_scale_x{swkbd_calc_arg_old.key_top_scale_x},
+ .key_top_scale_y{swkbd_calc_arg_old.key_top_scale_y},
+ .key_top_translate_x{swkbd_calc_arg_old.key_top_translate_x},
+ .key_top_translate_y{swkbd_calc_arg_old.key_top_translate_y},
.type{appear_arg.type},
.key_disable_flags{appear_arg.key_disable_flags},
- .key_top_as_floating{swkbd_calc_arg.key_top_as_floating},
- .enable_backspace_button{swkbd_calc_arg.enable_backspace_button},
+ .key_top_as_floating{swkbd_calc_arg_old.key_top_as_floating},
+ .enable_backspace_button{swkbd_calc_arg_old.enable_backspace_button},
.enable_return_button{appear_arg.enable_return_button},
.disable_cancel_button{appear_arg.disable_cancel_button},
};
- frontend.ShowInlineKeyboard(std::move(appear_parameters));
+ ShowInlineKeyboard(std::move(appear_parameters));
+}
- ChangeState(SwkbdState::InitializedIsShown);
+void SoftwareKeyboard::ShowInlineKeyboardNew() {
+ if (swkbd_state != SwkbdState::InitializedIsHidden) {
+ return;
+ }
+
+ ChangeState(SwkbdState::InitializedIsAppearing);
+
+ const auto& appear_arg = swkbd_calc_arg_new.appear_arg;
+
+ const u32 max_text_length =
+ appear_arg.max_text_length > 0 && appear_arg.max_text_length <= DEFAULT_MAX_TEXT_LENGTH
+ ? appear_arg.max_text_length
+ : DEFAULT_MAX_TEXT_LENGTH;
+
+ const u32 min_text_length =
+ appear_arg.min_text_length <= max_text_length ? appear_arg.min_text_length : 0;
+
+ Core::Frontend::InlineAppearParameters appear_parameters{
+ .max_text_length{max_text_length},
+ .min_text_length{min_text_length},
+ .key_top_scale_x{swkbd_calc_arg_new.key_top_scale_x},
+ .key_top_scale_y{swkbd_calc_arg_new.key_top_scale_y},
+ .key_top_translate_x{swkbd_calc_arg_new.key_top_translate_x},
+ .key_top_translate_y{swkbd_calc_arg_new.key_top_translate_y},
+ .type{appear_arg.type},
+ .key_disable_flags{appear_arg.key_disable_flags},
+ .key_top_as_floating{swkbd_calc_arg_new.key_top_as_floating},
+ .enable_backspace_button{swkbd_calc_arg_new.enable_backspace_button},
+ .enable_return_button{appear_arg.enable_return_button},
+ .disable_cancel_button{appear_arg.disable_cancel_button},
+ };
+
+ ShowInlineKeyboard(std::move(appear_parameters));
}
void SoftwareKeyboard::HideInlineKeyboard() {
@@ -693,6 +777,8 @@ void SoftwareKeyboard::RequestFinalize(const std::vector<u8>& request_data) {
void SoftwareKeyboard::RequestSetUserWordInfo(const std::vector<u8>& request_data) {
LOG_WARNING(Service_AM, "SetUserWordInfo is not implemented.");
+
+ ReplyReleasedUserWordInfo();
}
void SoftwareKeyboard::RequestSetCustomizeDic(const std::vector<u8>& request_data) {
@@ -702,53 +788,135 @@ void SoftwareKeyboard::RequestSetCustomizeDic(const std::vector<u8>& request_dat
void SoftwareKeyboard::RequestCalc(const std::vector<u8>& request_data) {
LOG_DEBUG(Service_AM, "Processing Request: Calc");
- ASSERT(request_data.size() == sizeof(SwkbdRequestCommand) + sizeof(SwkbdCalcArg));
+ ASSERT(request_data.size() >= sizeof(SwkbdRequestCommand) + sizeof(SwkbdCalcArgCommon));
- std::memcpy(&swkbd_calc_arg, request_data.data() + sizeof(SwkbdRequestCommand),
- sizeof(SwkbdCalcArg));
+ std::memcpy(&swkbd_calc_arg_common, request_data.data() + sizeof(SwkbdRequestCommand),
+ sizeof(SwkbdCalcArgCommon));
+
+ switch (swkbd_calc_arg_common.calc_arg_size) {
+ case sizeof(SwkbdCalcArgCommon) + sizeof(SwkbdCalcArgOld):
+ ASSERT(request_data.size() ==
+ sizeof(SwkbdRequestCommand) + sizeof(SwkbdCalcArgCommon) + sizeof(SwkbdCalcArgOld));
+ std::memcpy(&swkbd_calc_arg_old,
+ request_data.data() + sizeof(SwkbdRequestCommand) + sizeof(SwkbdCalcArgCommon),
+ sizeof(SwkbdCalcArgOld));
+ RequestCalcOld();
+ break;
+ case sizeof(SwkbdCalcArgCommon) + sizeof(SwkbdCalcArgNew):
+ ASSERT(request_data.size() ==
+ sizeof(SwkbdRequestCommand) + sizeof(SwkbdCalcArgCommon) + sizeof(SwkbdCalcArgNew));
+ std::memcpy(&swkbd_calc_arg_new,
+ request_data.data() + sizeof(SwkbdRequestCommand) + sizeof(SwkbdCalcArgCommon),
+ sizeof(SwkbdCalcArgNew));
+ RequestCalcNew();
+ break;
+ default:
+ UNIMPLEMENTED_MSG("Unknown SwkbdCalcArg size={}", swkbd_calc_arg_common.calc_arg_size);
+ ASSERT(request_data.size() >=
+ sizeof(SwkbdRequestCommand) + sizeof(SwkbdCalcArgCommon) + sizeof(SwkbdCalcArgNew));
+ std::memcpy(&swkbd_calc_arg_new,
+ request_data.data() + sizeof(SwkbdRequestCommand) + sizeof(SwkbdCalcArgCommon),
+ sizeof(SwkbdCalcArgNew));
+ RequestCalcNew();
+ break;
+ }
+}
+
+void SoftwareKeyboard::RequestCalcOld() {
+ if (swkbd_calc_arg_common.flags.set_input_text) {
+ current_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(
+ swkbd_calc_arg_old.input_text.data(), swkbd_calc_arg_old.input_text.size());
+ }
+
+ if (swkbd_calc_arg_common.flags.set_cursor_position) {
+ current_cursor_position = swkbd_calc_arg_old.cursor_position;
+ }
+
+ if (swkbd_calc_arg_common.flags.set_utf8_mode) {
+ inline_use_utf8 = swkbd_calc_arg_old.utf8_mode;
+ }
+
+ if (swkbd_state <= SwkbdState::InitializedIsHidden &&
+ swkbd_calc_arg_common.flags.unset_customize_dic) {
+ ReplyUnsetCustomizeDic();
+ }
+
+ if (swkbd_state <= SwkbdState::InitializedIsHidden &&
+ swkbd_calc_arg_common.flags.unset_user_word_info) {
+ ReplyReleasedUserWordInfo();
+ }
+
+ if (swkbd_state == SwkbdState::NotInitialized &&
+ swkbd_calc_arg_common.flags.set_initialize_arg) {
+ InitializeFrontendInlineKeyboardOld();
+
+ ChangeState(SwkbdState::InitializedIsHidden);
+
+ ReplyFinishedInitialize();
+ }
+
+ if (!swkbd_calc_arg_common.flags.set_initialize_arg &&
+ (swkbd_calc_arg_common.flags.set_input_text ||
+ swkbd_calc_arg_common.flags.set_cursor_position)) {
+ InlineTextChanged();
+ }
+
+ if (swkbd_state == SwkbdState::InitializedIsHidden && swkbd_calc_arg_common.flags.appear) {
+ ShowInlineKeyboardOld();
+ return;
+ }
+
+ if (swkbd_state == SwkbdState::InitializedIsShown && swkbd_calc_arg_common.flags.disappear) {
+ HideInlineKeyboard();
+ return;
+ }
+}
- if (swkbd_calc_arg.flags.set_input_text) {
+void SoftwareKeyboard::RequestCalcNew() {
+ if (swkbd_calc_arg_common.flags.set_input_text) {
current_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(
- swkbd_calc_arg.input_text.data(), swkbd_calc_arg.input_text.size());
+ swkbd_calc_arg_new.input_text.data(), swkbd_calc_arg_new.input_text.size());
}
- if (swkbd_calc_arg.flags.set_cursor_position) {
- current_cursor_position = swkbd_calc_arg.cursor_position;
+ if (swkbd_calc_arg_common.flags.set_cursor_position) {
+ current_cursor_position = swkbd_calc_arg_new.cursor_position;
}
- if (swkbd_calc_arg.flags.set_utf8_mode) {
- inline_use_utf8 = swkbd_calc_arg.utf8_mode;
+ if (swkbd_calc_arg_common.flags.set_utf8_mode) {
+ inline_use_utf8 = swkbd_calc_arg_new.utf8_mode;
}
if (swkbd_state <= SwkbdState::InitializedIsHidden &&
- swkbd_calc_arg.flags.unset_customize_dic) {
+ swkbd_calc_arg_common.flags.unset_customize_dic) {
ReplyUnsetCustomizeDic();
}
if (swkbd_state <= SwkbdState::InitializedIsHidden &&
- swkbd_calc_arg.flags.unset_user_word_info) {
+ swkbd_calc_arg_common.flags.unset_user_word_info) {
ReplyReleasedUserWordInfo();
}
- if (swkbd_state == SwkbdState::NotInitialized && swkbd_calc_arg.flags.set_initialize_arg) {
- InitializeFrontendKeyboard();
+ if (swkbd_state == SwkbdState::NotInitialized &&
+ swkbd_calc_arg_common.flags.set_initialize_arg) {
+ InitializeFrontendInlineKeyboardNew();
ChangeState(SwkbdState::InitializedIsHidden);
ReplyFinishedInitialize();
}
- if (!swkbd_calc_arg.flags.set_initialize_arg &&
- (swkbd_calc_arg.flags.set_input_text || swkbd_calc_arg.flags.set_cursor_position)) {
+ if (!swkbd_calc_arg_common.flags.set_initialize_arg &&
+ (swkbd_calc_arg_common.flags.set_input_text ||
+ swkbd_calc_arg_common.flags.set_cursor_position)) {
InlineTextChanged();
}
- if (swkbd_state == SwkbdState::InitializedIsHidden && swkbd_calc_arg.flags.appear) {
- ShowInlineKeyboard();
+ if (swkbd_state == SwkbdState::InitializedIsHidden && swkbd_calc_arg_common.flags.appear) {
+ ShowInlineKeyboardNew();
return;
}
- if (swkbd_state == SwkbdState::InitializedIsShown && swkbd_calc_arg.flags.disappear) {
+ if (swkbd_state == SwkbdState::InitializedIsShown && swkbd_calc_arg_common.flags.disappear) {
HideInlineKeyboard();
return;
}
diff --git a/src/core/hle/service/am/applets/applet_software_keyboard.h b/src/core/hle/service/am/applets/applet_software_keyboard.h
index a0fddd965..7ee4c5eea 100644
--- a/src/core/hle/service/am/applets/applet_software_keyboard.h
+++ b/src/core/hle/service/am/applets/applet_software_keyboard.h
@@ -13,6 +13,11 @@ namespace Core {
class System;
}
+namespace Core::Frontend {
+struct KeyboardInitializeParameters;
+struct InlineAppearParameters;
+} // namespace Core::Frontend
+
namespace Service::AM::Applets {
class SoftwareKeyboard final : public Applet {
@@ -78,13 +83,22 @@ private:
void ChangeState(SwkbdState state);
/**
- * Signals the frontend to initialize the software keyboard with common parameters.
- * This initializes either the normal software keyboard or the inline software keyboard
- * depending on the state of is_background.
+ * Signals the frontend to initialize the normal software keyboard with common parameters.
* Note that this does not cause the keyboard to appear.
- * Use the respective Show*Keyboard() functions to cause the respective keyboards to appear.
+ * Use the ShowNormalKeyboard() functions to cause the keyboard to appear.
*/
- void InitializeFrontendKeyboard();
+ void InitializeFrontendNormalKeyboard();
+
+ /**
+ * Signals the frontend to initialize the inline software keyboard with common parameters.
+ * Note that this does not cause the keyboard to appear.
+ * Use the ShowInlineKeyboard() to cause the keyboard to appear.
+ */
+ void InitializeFrontendInlineKeyboard(
+ Core::Frontend::KeyboardInitializeParameters initialize_parameters);
+
+ void InitializeFrontendInlineKeyboardOld();
+ void InitializeFrontendInlineKeyboardNew();
/// Signals the frontend to show the normal software keyboard.
void ShowNormalKeyboard();
@@ -94,7 +108,10 @@ private:
std::u16string text_check_message);
/// Signals the frontend to show the inline software keyboard.
- void ShowInlineKeyboard();
+ void ShowInlineKeyboard(Core::Frontend::InlineAppearParameters appear_parameters);
+
+ void ShowInlineKeyboardOld();
+ void ShowInlineKeyboardNew();
/// Signals the frontend to hide the inline software keyboard.
void HideInlineKeyboard();
@@ -111,6 +128,8 @@ private:
void RequestSetUserWordInfo(const std::vector<u8>& request_data);
void RequestSetCustomizeDic(const std::vector<u8>& request_data);
void RequestCalc(const std::vector<u8>& request_data);
+ void RequestCalcOld();
+ void RequestCalcNew();
void RequestSetCustomizedDictionaries(const std::vector<u8>& request_data);
void RequestUnsetCustomizedDictionaries(const std::vector<u8>& request_data);
void RequestSetChangedStringV2Flag(const std::vector<u8>& request_data);
@@ -149,7 +168,9 @@ private:
SwkbdState swkbd_state{SwkbdState::NotInitialized};
SwkbdInitializeArg swkbd_initialize_arg;
- SwkbdCalcArg swkbd_calc_arg;
+ SwkbdCalcArgCommon swkbd_calc_arg_common;
+ SwkbdCalcArgOld swkbd_calc_arg_old;
+ SwkbdCalcArgNew swkbd_calc_arg_new;
bool use_changed_string_v2{false};
bool use_moved_cursor_v2{false};
bool inline_use_utf8{false};
diff --git a/src/core/hle/service/am/applets/applet_software_keyboard_types.h b/src/core/hle/service/am/applets/applet_software_keyboard_types.h
index 21aa8e800..de1ca7aa5 100644
--- a/src/core/hle/service/am/applets/applet_software_keyboard_types.h
+++ b/src/core/hle/service/am/applets/applet_software_keyboard_types.h
@@ -10,6 +10,7 @@
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
+#include "common/uuid.h"
namespace Service::AM::Applets {
@@ -216,7 +217,7 @@ struct SwkbdInitializeArg {
};
static_assert(sizeof(SwkbdInitializeArg) == 0x8, "SwkbdInitializeArg has incorrect size.");
-struct SwkbdAppearArg {
+struct SwkbdAppearArgOld {
SwkbdType type{};
std::array<char16_t, MAX_OK_TEXT_LENGTH + 1> ok_text{};
char16_t left_optional_symbol_key{};
@@ -229,19 +230,76 @@ struct SwkbdAppearArg {
bool enable_return_button{};
INSERT_PADDING_BYTES(3);
u32 flags{};
- INSERT_PADDING_WORDS(6);
+ bool is_use_save_data{};
+ INSERT_PADDING_BYTES(7);
+ Common::UUID user_id{};
};
-static_assert(sizeof(SwkbdAppearArg) == 0x48, "SwkbdAppearArg has incorrect size.");
+static_assert(sizeof(SwkbdAppearArgOld) == 0x48, "SwkbdAppearArg has incorrect size.");
-struct SwkbdCalcArg {
+struct SwkbdAppearArgNew {
+ SwkbdType type{};
+ std::array<char16_t, MAX_OK_TEXT_LENGTH + 1> ok_text{};
+ char16_t left_optional_symbol_key{};
+ char16_t right_optional_symbol_key{};
+ bool use_prediction{};
+ bool disable_cancel_button{};
+ SwkbdKeyDisableFlags key_disable_flags{};
+ u32 max_text_length{};
+ u32 min_text_length{};
+ bool enable_return_button{};
+ INSERT_PADDING_BYTES(3);
+ u32 flags{};
+ bool is_use_save_data{};
+ INSERT_PADDING_BYTES(7);
+ Common::UUID user_id{};
+ u64 start_sampling_number{};
+ INSERT_PADDING_WORDS(8);
+};
+static_assert(sizeof(SwkbdAppearArgNew) == 0x70, "SwkbdAppearArg has incorrect size.");
+
+struct SwkbdCalcArgCommon {
u32 unknown{};
u16 calc_arg_size{};
INSERT_PADDING_BYTES(2);
SwkbdCalcArgFlags flags{};
SwkbdInitializeArg initialize_arg{};
+};
+static_assert(sizeof(SwkbdCalcArgCommon) == 0x18, "SwkbdCalcArgCommon has incorrect size.");
+
+struct SwkbdCalcArgOld {
+ f32 volume{};
+ s32 cursor_position{};
+ SwkbdAppearArgOld appear_arg{};
+ std::array<char16_t, 0x1FA> input_text{};
+ bool utf8_mode{};
+ INSERT_PADDING_BYTES(1);
+ bool enable_backspace_button{};
+ INSERT_PADDING_BYTES(3);
+ bool key_top_as_floating{};
+ bool footer_scalable{};
+ bool alpha_enabled_in_input_mode{};
+ u8 input_mode_fade_type{};
+ bool disable_touch{};
+ bool disable_hardware_keyboard{};
+ INSERT_PADDING_BYTES(8);
+ f32 key_top_scale_x{};
+ f32 key_top_scale_y{};
+ f32 key_top_translate_x{};
+ f32 key_top_translate_y{};
+ f32 key_top_bg_alpha{};
+ f32 footer_bg_alpha{};
+ f32 balloon_scale{};
+ INSERT_PADDING_WORDS(4);
+ u8 se_group{};
+ INSERT_PADDING_BYTES(3);
+};
+static_assert(sizeof(SwkbdCalcArgOld) == 0x4A0 - sizeof(SwkbdCalcArgCommon),
+ "SwkbdCalcArgOld has incorrect size.");
+
+struct SwkbdCalcArgNew {
+ SwkbdAppearArgNew appear_arg{};
f32 volume{};
s32 cursor_position{};
- SwkbdAppearArg appear_arg{};
std::array<char16_t, 0x1FA> input_text{};
bool utf8_mode{};
INSERT_PADDING_BYTES(1);
@@ -264,8 +322,10 @@ struct SwkbdCalcArg {
INSERT_PADDING_WORDS(4);
u8 se_group{};
INSERT_PADDING_BYTES(3);
+ INSERT_PADDING_WORDS(8);
};
-static_assert(sizeof(SwkbdCalcArg) == 0x4A0, "SwkbdCalcArg has incorrect size.");
+static_assert(sizeof(SwkbdCalcArgNew) == 0x4E8 - sizeof(SwkbdCalcArgCommon),
+ "SwkbdCalcArgNew has incorrect size.");
struct SwkbdChangedStringArg {
u32 text_length{};
diff --git a/src/core/hle/service/am/applets/applet_web_browser.cpp b/src/core/hle/service/am/applets/applet_web_browser.cpp
index bb5cb61be..a4b3fb187 100644
--- a/src/core/hle/service/am/applets/applet_web_browser.cpp
+++ b/src/core/hle/service/am/applets/applet_web_browser.cpp
@@ -446,6 +446,14 @@ void WebBrowser::ExecuteLogin() {
}
void WebBrowser::ExecuteOffline() {
+ // TODO (Morph): This is a hack for WebSession foreground web applets such as those used by
+ // Super Mario 3D All-Stars.
+ // TODO (Morph): Implement WebSession.
+ if (applet_mode == LibraryAppletMode::AllForegroundInitiallyHidden) {
+ LOG_WARNING(Service_AM, "WebSession is not implemented");
+ return;
+ }
+
const auto main_url = GetMainURL(Common::FS::PathToUTF8String(offline_document));
if (!Common::FS::Exists(main_url)) {
diff --git a/src/core/hle/service/am/applets/applets.cpp b/src/core/hle/service/am/applets/applets.cpp
index 79e62679d..1f4c9786a 100644
--- a/src/core/hle/service/am/applets/applets.cpp
+++ b/src/core/hle/service/am/applets/applets.cpp
@@ -9,7 +9,7 @@
#include "core/frontend/applets/controller.h"
#include "core/frontend/applets/error.h"
#include "core/frontend/applets/general_frontend.h"
-#include "core/frontend/applets/mii.h"
+#include "core/frontend/applets/mii_edit.h"
#include "core/frontend/applets/profile_select.h"
#include "core/frontend/applets/software_keyboard.h"
#include "core/frontend/applets/web_browser.h"
@@ -20,7 +20,7 @@
#include "core/hle/service/am/applets/applet_controller.h"
#include "core/hle/service/am/applets/applet_error.h"
#include "core/hle/service/am/applets/applet_general_backend.h"
-#include "core/hle/service/am/applets/applet_mii.h"
+#include "core/hle/service/am/applets/applet_mii_edit.h"
#include "core/hle/service/am/applets/applet_profile_select.h"
#include "core/hle/service/am/applets/applet_software_keyboard.h"
#include "core/hle/service/am/applets/applet_web_browser.h"
@@ -173,12 +173,12 @@ void Applet::Initialize() {
AppletFrontendSet::AppletFrontendSet() = default;
AppletFrontendSet::AppletFrontendSet(ControllerApplet controller_applet, ErrorApplet error_applet,
+ MiiEdit mii_edit_,
ParentalControlsApplet parental_controls_applet,
- MiiApplet mii_applet, PhotoViewer photo_viewer_,
- ProfileSelect profile_select_,
+ PhotoViewer photo_viewer_, ProfileSelect profile_select_,
SoftwareKeyboard software_keyboard_, WebBrowser web_browser_)
: controller{std::move(controller_applet)}, error{std::move(error_applet)},
- parental_controls{std::move(parental_controls_applet)}, mii{std::move(mii_applet)},
+ mii_edit{std::move(mii_edit_)}, parental_controls{std::move(parental_controls_applet)},
photo_viewer{std::move(photo_viewer_)}, profile_select{std::move(profile_select_)},
software_keyboard{std::move(software_keyboard_)}, web_browser{std::move(web_browser_)} {}
@@ -205,12 +205,12 @@ void AppletManager::SetAppletFrontendSet(AppletFrontendSet set) {
frontend.error = std::move(set.error);
}
- if (set.parental_controls != nullptr) {
- frontend.parental_controls = std::move(set.parental_controls);
+ if (set.mii_edit != nullptr) {
+ frontend.mii_edit = std::move(set.mii_edit);
}
- if (set.mii != nullptr) {
- frontend.mii = std::move(set.mii);
+ if (set.parental_controls != nullptr) {
+ frontend.parental_controls = std::move(set.parental_controls);
}
if (set.photo_viewer != nullptr) {
@@ -245,15 +245,15 @@ void AppletManager::SetDefaultAppletsIfMissing() {
frontend.error = std::make_unique<Core::Frontend::DefaultErrorApplet>();
}
+ if (frontend.mii_edit == nullptr) {
+ frontend.mii_edit = std::make_unique<Core::Frontend::DefaultMiiEditApplet>();
+ }
+
if (frontend.parental_controls == nullptr) {
frontend.parental_controls =
std::make_unique<Core::Frontend::DefaultParentalControlsApplet>();
}
- if (frontend.mii == nullptr) {
- frontend.mii = std::make_unique<Core::Frontend::DefaultMiiApplet>();
- }
-
if (frontend.photo_viewer == nullptr) {
frontend.photo_viewer = std::make_unique<Core::Frontend::DefaultPhotoViewerApplet>();
}
@@ -289,7 +289,7 @@ std::shared_ptr<Applet> AppletManager::GetApplet(AppletId id, LibraryAppletMode
case AppletId::SoftwareKeyboard:
return std::make_shared<SoftwareKeyboard>(system, mode, *frontend.software_keyboard);
case AppletId::MiiEdit:
- return std::make_shared<Mii>(system, mode, *frontend.mii);
+ return std::make_shared<MiiEdit>(system, mode, *frontend.mii_edit);
case AppletId::Web:
case AppletId::Shop:
case AppletId::OfflineWeb:
diff --git a/src/core/hle/service/am/applets/applets.h b/src/core/hle/service/am/applets/applets.h
index 0c44aec79..50a7bdceb 100644
--- a/src/core/hle/service/am/applets/applets.h
+++ b/src/core/hle/service/am/applets/applets.h
@@ -20,8 +20,8 @@ namespace Core::Frontend {
class ControllerApplet;
class ECommerceApplet;
class ErrorApplet;
+class MiiEditApplet;
class ParentalControlsApplet;
-class MiiApplet;
class PhotoViewerApplet;
class ProfileSelectApplet;
class SoftwareKeyboardApplet;
@@ -179,8 +179,8 @@ protected:
struct AppletFrontendSet {
using ControllerApplet = std::unique_ptr<Core::Frontend::ControllerApplet>;
using ErrorApplet = std::unique_ptr<Core::Frontend::ErrorApplet>;
+ using MiiEdit = std::unique_ptr<Core::Frontend::MiiEditApplet>;
using ParentalControlsApplet = std::unique_ptr<Core::Frontend::ParentalControlsApplet>;
- using MiiApplet = std::unique_ptr<Core::Frontend::MiiApplet>;
using PhotoViewer = std::unique_ptr<Core::Frontend::PhotoViewerApplet>;
using ProfileSelect = std::unique_ptr<Core::Frontend::ProfileSelectApplet>;
using SoftwareKeyboard = std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet>;
@@ -188,7 +188,7 @@ struct AppletFrontendSet {
AppletFrontendSet();
AppletFrontendSet(ControllerApplet controller_applet, ErrorApplet error_applet,
- ParentalControlsApplet parental_controls_applet, MiiApplet mii_applet,
+ MiiEdit mii_edit_, ParentalControlsApplet parental_controls_applet,
PhotoViewer photo_viewer_, ProfileSelect profile_select_,
SoftwareKeyboard software_keyboard_, WebBrowser web_browser_);
~AppletFrontendSet();
@@ -201,8 +201,8 @@ struct AppletFrontendSet {
ControllerApplet controller;
ErrorApplet error;
+ MiiEdit mii_edit;
ParentalControlsApplet parental_controls;
- MiiApplet mii;
PhotoViewer photo_viewer;
ProfileSelect profile_select;
SoftwareKeyboard software_keyboard;
diff --git a/src/core/hle/service/audio/audout_u.cpp b/src/core/hle/service/audio/audout_u.cpp
index affa7971c..a72956a28 100644
--- a/src/core/hle/service/audio/audout_u.cpp
+++ b/src/core/hle/service/audio/audout_u.cpp
@@ -41,9 +41,10 @@ public:
explicit IAudioOut(Core::System& system_, AudoutParams audio_params_,
AudioCore::AudioOut& audio_core_, std::string&& device_name_,
std::string&& unique_name)
- : ServiceFramework{system_, "IAudioOut"}, audio_core{audio_core_},
- device_name{std::move(device_name_)}, audio_params{audio_params_},
- main_memory{system.Memory()}, service_context{system_, "IAudioOut"} {
+ : ServiceFramework{system_, "IAudioOut", ServiceThreadType::CreateNew},
+ audio_core{audio_core_}, device_name{std::move(device_name_)},
+ audio_params{audio_params_}, main_memory{system.Memory()}, service_context{system_,
+ "IAudioOut"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IAudioOut::GetAudioOutState, "GetAudioOutState"},
diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp
index f45e5cecc..d4ffeb21d 100644
--- a/src/core/hle/service/audio/audren_u.cpp
+++ b/src/core/hle/service/audio/audren_u.cpp
@@ -24,7 +24,8 @@ public:
explicit IAudioRenderer(Core::System& system_,
const AudioCommon::AudioRendererParameter& audren_params,
const std::size_t instance_number)
- : ServiceFramework{system_, "IAudioRenderer"}, service_context{system_, "IAudioRenderer"} {
+ : ServiceFramework{system_, "IAudioRenderer", ServiceThreadType::CreateNew},
+ service_context{system_, "IAudioRenderer"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IAudioRenderer::GetSampleRate, "GetSampleRate"},
diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp
index 3703ca4c6..4208337db 100644
--- a/src/core/hle/service/filesystem/filesystem.cpp
+++ b/src/core/hle/service/filesystem/filesystem.cpp
@@ -174,7 +174,7 @@ ResultCode VfsDirectoryServiceWrapper::RenameFile(const std::string& src_path_,
ASSERT_MSG(dest != nullptr, "Newly created file with success cannot be found.");
ASSERT_MSG(dest->WriteBytes(src->ReadAllBytes()) == src->GetSize(),
- "Could not write all of the bytes but everything else has succeded.");
+ "Could not write all of the bytes but everything else has succeeded.");
if (!src->GetContainingDirectory()->DeleteFile(Common::FS::GetFilename(src_path))) {
// TODO(DarkLordZach): Find a better error code for this
diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp
index b087e7bba..c07929ab8 100644
--- a/src/core/hle/service/filesystem/fsp_srv.cpp
+++ b/src/core/hle/service/filesystem/fsp_srv.cpp
@@ -58,7 +58,8 @@ enum class FileSystemType : u8 {
class IStorage final : public ServiceFramework<IStorage> {
public:
explicit IStorage(Core::System& system_, FileSys::VirtualFile backend_)
- : ServiceFramework{system_, "IStorage"}, backend(std::move(backend_)) {
+ : ServiceFramework{system_, "IStorage", ServiceThreadType::CreateNew},
+ backend(std::move(backend_)) {
static const FunctionInfo functions[] = {
{0, &IStorage::Read, "Read"},
{1, nullptr, "Write"},
@@ -116,7 +117,8 @@ private:
class IFile final : public ServiceFramework<IFile> {
public:
explicit IFile(Core::System& system_, FileSys::VirtualFile backend_)
- : ServiceFramework{system_, "IFile"}, backend(std::move(backend_)) {
+ : ServiceFramework{system_, "IFile", ServiceThreadType::CreateNew},
+ backend(std::move(backend_)) {
static const FunctionInfo functions[] = {
{0, &IFile::Read, "Read"},
{1, &IFile::Write, "Write"},
@@ -252,7 +254,8 @@ static void BuildEntryIndex(std::vector<FileSys::Entry>& entries, const std::vec
class IDirectory final : public ServiceFramework<IDirectory> {
public:
explicit IDirectory(Core::System& system_, FileSys::VirtualDir backend_)
- : ServiceFramework{system_, "IDirectory"}, backend(std::move(backend_)) {
+ : ServiceFramework{system_, "IDirectory", ServiceThreadType::CreateNew},
+ backend(std::move(backend_)) {
static const FunctionInfo functions[] = {
{0, &IDirectory::Read, "Read"},
{1, &IDirectory::GetEntryCount, "GetEntryCount"},
@@ -308,8 +311,8 @@ private:
class IFileSystem final : public ServiceFramework<IFileSystem> {
public:
explicit IFileSystem(Core::System& system_, FileSys::VirtualDir backend_, SizeGetter size_)
- : ServiceFramework{system_, "IFileSystem"}, backend{std::move(backend_)}, size{std::move(
- size_)} {
+ : ServiceFramework{system_, "IFileSystem", ServiceThreadType::CreateNew},
+ backend{std::move(backend_)}, size{std::move(size_)} {
static const FunctionInfo functions[] = {
{0, &IFileSystem::CreateFile, "CreateFile"},
{1, &IFileSystem::DeleteFile, "DeleteFile"},
diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp
index e5c951e06..68407e39a 100644
--- a/src/core/hle/service/hid/controllers/npad.cpp
+++ b/src/core/hle/service/hid/controllers/npad.cpp
@@ -17,6 +17,7 @@
#include "core/hle/kernel/k_readable_event.h"
#include "core/hle/kernel/k_writable_event.h"
#include "core/hle/service/hid/controllers/npad.h"
+#include "core/hle/service/hid/errors.h"
#include "core/hle/service/kernel_helpers.h"
namespace Service::HID {
@@ -48,15 +49,17 @@ bool Controller_NPad::IsNpadIdValid(Core::HID::NpadIdType npad_id) {
}
bool Controller_NPad::IsDeviceHandleValid(const Core::HID::VibrationDeviceHandle& device_handle) {
- return IsNpadIdValid(static_cast<Core::HID::NpadIdType>(device_handle.npad_id)) &&
- device_handle.npad_type < Core::HID::NpadStyleIndex::MaxNpadType &&
- device_handle.device_index < Core::HID::DeviceIndex::MaxDeviceIndex;
+ const auto npad_id = IsNpadIdValid(static_cast<Core::HID::NpadIdType>(device_handle.npad_id));
+ const bool npad_type = device_handle.npad_type < Core::HID::NpadStyleIndex::MaxNpadType;
+ const bool device_index = device_handle.device_index < Core::HID::DeviceIndex::MaxDeviceIndex;
+ return npad_id && npad_type && device_index;
}
bool Controller_NPad::IsDeviceHandleValid(const Core::HID::SixAxisSensorHandle& device_handle) {
- return IsNpadIdValid(static_cast<Core::HID::NpadIdType>(device_handle.npad_id)) &&
- device_handle.npad_type < Core::HID::NpadStyleIndex::MaxNpadType &&
- device_handle.device_index < Core::HID::DeviceIndex::MaxDeviceIndex;
+ const auto npad_id = IsNpadIdValid(static_cast<Core::HID::NpadIdType>(device_handle.npad_id));
+ const bool npad_type = device_handle.npad_type < Core::HID::NpadStyleIndex::MaxNpadType;
+ const bool device_index = device_handle.device_index < Core::HID::DeviceIndex::MaxDeviceIndex;
+ return npad_id && npad_type && device_index;
}
Controller_NPad::Controller_NPad(Core::HID::HIDCore& hid_core_,
@@ -262,11 +265,6 @@ void Controller_NPad::OnInit() {
service_context.CreateEvent(fmt::format("npad:NpadStyleSetChanged_{}", i));
}
- if (hid_core.GetSupportedStyleTag().raw == Core::HID::NpadStyleSet::None) {
- // We want to support all controllers
- hid_core.SetSupportedStyleTag({Core::HID::NpadStyleSet::All});
- }
-
supported_npad_id_types.resize(npad_id_list.size());
std::memcpy(supported_npad_id_types.data(), npad_id_list.data(),
npad_id_list.size() * sizeof(Core::HID::NpadIdType));
@@ -288,14 +286,6 @@ void Controller_NPad::OnInit() {
WriteEmptyEntry(npad);
}
}
-
- // Connect controllers
- for (auto& controller : controller_data) {
- const auto& device = controller.device;
- if (device->IsConnected()) {
- AddNewControllerAt(device->GetNpadStyleIndex(), device->GetNpadIdType());
- }
- }
}
void Controller_NPad::WriteEmptyEntry(NpadInternalState& npad) {
@@ -320,6 +310,7 @@ void Controller_NPad::WriteEmptyEntry(NpadInternalState& npad) {
}
void Controller_NPad::OnRelease() {
+ is_controller_initialized = false;
for (std::size_t i = 0; i < controller_data.size(); ++i) {
auto& controller = controller_data[i];
service_context.CloseEvent(controller.styleset_changed_event);
@@ -330,7 +321,7 @@ void Controller_NPad::OnRelease() {
}
void Controller_NPad::RequestPadStateUpdate(Core::HID::NpadIdType npad_id) {
- std::lock_guard lock{mutex};
+ std::scoped_lock lock{mutex};
auto& controller = GetControllerFromNpadIdType(npad_id);
const auto controller_type = controller.device->GetNpadStyleIndex();
if (!controller.device->IsConnected()) {
@@ -651,9 +642,27 @@ void Controller_NPad::OnMotionUpdate(const Core::Timing::CoreTiming& core_timing
void Controller_NPad::SetSupportedStyleSet(Core::HID::NpadStyleTag style_set) {
hid_core.SetSupportedStyleTag(style_set);
+
+ if (is_controller_initialized) {
+ return;
+ }
+
+ // Once SetSupportedStyleSet is called controllers are fully initialized
+ is_controller_initialized = true;
+
+ // Connect all active controllers
+ for (auto& controller : controller_data) {
+ const auto& device = controller.device;
+ if (device->IsConnected()) {
+ AddNewControllerAt(device->GetNpadStyleIndex(), device->GetNpadIdType());
+ }
+ }
}
Core::HID::NpadStyleTag Controller_NPad::GetSupportedStyleSet() const {
+ if (!is_controller_initialized) {
+ return {Core::HID::NpadStyleSet::None};
+ }
return hid_core.GetSupportedStyleTag();
}
@@ -1001,87 +1010,271 @@ void Controller_NPad::DisconnectNpad(Core::HID::NpadIdType npad_id) {
WriteEmptyEntry(controller.shared_memory_entry);
}
-void Controller_NPad::SetGyroscopeZeroDriftMode(Core::HID::SixAxisSensorHandle sixaxis_handle,
- GyroscopeZeroDriftMode drift_mode) {
+ResultCode Controller_NPad::SetGyroscopeZeroDriftMode(Core::HID::SixAxisSensorHandle sixaxis_handle,
+ GyroscopeZeroDriftMode drift_mode) {
if (!IsDeviceHandleValid(sixaxis_handle)) {
LOG_ERROR(Service_HID, "Invalid handle");
- return;
+ return NpadInvalidHandle;
+ }
+
+ auto& controller = GetControllerFromHandle(sixaxis_handle);
+ switch (sixaxis_handle.npad_type) {
+ case Core::HID::NpadStyleIndex::ProController:
+ controller.sixaxis_fullkey.gyroscope_zero_drift_mode = drift_mode;
+ break;
+ case Core::HID::NpadStyleIndex::Handheld:
+ controller.sixaxis_handheld.gyroscope_zero_drift_mode = drift_mode;
+ break;
+ case Core::HID::NpadStyleIndex::JoyconDual:
+ case Core::HID::NpadStyleIndex::GameCube:
+ case Core::HID::NpadStyleIndex::Pokeball:
+ if (sixaxis_handle.device_index == Core::HID::DeviceIndex::Left) {
+ controller.sixaxis_dual_left.gyroscope_zero_drift_mode = drift_mode;
+ break;
+ }
+ controller.sixaxis_dual_right.gyroscope_zero_drift_mode = drift_mode;
+ break;
+ case Core::HID::NpadStyleIndex::JoyconLeft:
+ controller.sixaxis_left.gyroscope_zero_drift_mode = drift_mode;
+ break;
+ case Core::HID::NpadStyleIndex::JoyconRight:
+ controller.sixaxis_right.gyroscope_zero_drift_mode = drift_mode;
+ break;
+ default:
+ LOG_ERROR(Service_HID, "Invalid Npad type {}", sixaxis_handle.npad_type);
+ return NpadInvalidHandle;
+ }
+
+ return ResultSuccess;
+}
+
+ResultCode Controller_NPad::GetGyroscopeZeroDriftMode(Core::HID::SixAxisSensorHandle sixaxis_handle,
+ GyroscopeZeroDriftMode& drift_mode) const {
+ if (!IsDeviceHandleValid(sixaxis_handle)) {
+ LOG_ERROR(Service_HID, "Invalid handle");
+ return NpadInvalidHandle;
}
+
auto& controller = GetControllerFromHandle(sixaxis_handle);
- controller.gyroscope_zero_drift_mode = drift_mode;
+ switch (sixaxis_handle.npad_type) {
+ case Core::HID::NpadStyleIndex::ProController:
+ drift_mode = controller.sixaxis_fullkey.gyroscope_zero_drift_mode;
+ break;
+ case Core::HID::NpadStyleIndex::Handheld:
+ drift_mode = controller.sixaxis_handheld.gyroscope_zero_drift_mode;
+ break;
+ case Core::HID::NpadStyleIndex::JoyconDual:
+ case Core::HID::NpadStyleIndex::GameCube:
+ case Core::HID::NpadStyleIndex::Pokeball:
+ if (sixaxis_handle.device_index == Core::HID::DeviceIndex::Left) {
+ drift_mode = controller.sixaxis_dual_left.gyroscope_zero_drift_mode;
+ break;
+ }
+ drift_mode = controller.sixaxis_dual_right.gyroscope_zero_drift_mode;
+ break;
+ case Core::HID::NpadStyleIndex::JoyconLeft:
+ drift_mode = controller.sixaxis_left.gyroscope_zero_drift_mode;
+ break;
+ case Core::HID::NpadStyleIndex::JoyconRight:
+ drift_mode = controller.sixaxis_right.gyroscope_zero_drift_mode;
+ break;
+ default:
+ LOG_ERROR(Service_HID, "Invalid Npad type {}", sixaxis_handle.npad_type);
+ return NpadInvalidHandle;
+ }
+
+ return ResultSuccess;
}
-Controller_NPad::GyroscopeZeroDriftMode Controller_NPad::GetGyroscopeZeroDriftMode(
- Core::HID::SixAxisSensorHandle sixaxis_handle) const {
+ResultCode Controller_NPad::IsSixAxisSensorAtRest(Core::HID::SixAxisSensorHandle sixaxis_handle,
+ bool& is_at_rest) const {
if (!IsDeviceHandleValid(sixaxis_handle)) {
LOG_ERROR(Service_HID, "Invalid handle");
- // Return the default value
- return GyroscopeZeroDriftMode::Standard;
+ return NpadInvalidHandle;
}
const auto& controller = GetControllerFromHandle(sixaxis_handle);
- return controller.gyroscope_zero_drift_mode;
+ is_at_rest = controller.sixaxis_at_rest;
+ return ResultSuccess;
}
-bool Controller_NPad::IsSixAxisSensorAtRest(Core::HID::SixAxisSensorHandle sixaxis_handle) const {
+ResultCode Controller_NPad::IsFirmwareUpdateAvailableForSixAxisSensor(
+ Core::HID::SixAxisSensorHandle sixaxis_handle, bool& is_firmware_available) const {
if (!IsDeviceHandleValid(sixaxis_handle)) {
LOG_ERROR(Service_HID, "Invalid handle");
- // Return the default value
- return true;
+ return NpadInvalidHandle;
}
- const auto& controller = GetControllerFromHandle(sixaxis_handle);
- return controller.sixaxis_at_rest;
+
+ // We don't support joycon firmware updates
+ is_firmware_available = false;
+ return ResultSuccess;
}
-void Controller_NPad::SetSixAxisEnabled(Core::HID::SixAxisSensorHandle sixaxis_handle,
- bool sixaxis_status) {
+ResultCode Controller_NPad::SetSixAxisEnabled(Core::HID::SixAxisSensorHandle sixaxis_handle,
+ bool sixaxis_status) {
if (!IsDeviceHandleValid(sixaxis_handle)) {
LOG_ERROR(Service_HID, "Invalid handle");
- return;
+ return NpadInvalidHandle;
}
auto& controller = GetControllerFromHandle(sixaxis_handle);
controller.sixaxis_sensor_enabled = sixaxis_status;
+ return ResultSuccess;
}
-void Controller_NPad::SetSixAxisFusionEnabled(Core::HID::SixAxisSensorHandle sixaxis_handle,
- bool sixaxis_fusion_status) {
+ResultCode Controller_NPad::IsSixAxisSensorFusionEnabled(
+ Core::HID::SixAxisSensorHandle sixaxis_handle, bool& is_fusion_enabled) const {
if (!IsDeviceHandleValid(sixaxis_handle)) {
LOG_ERROR(Service_HID, "Invalid handle");
- return;
+ return NpadInvalidHandle;
}
+
auto& controller = GetControllerFromHandle(sixaxis_handle);
- controller.sixaxis_fusion_enabled = sixaxis_fusion_status;
-}
+ switch (sixaxis_handle.npad_type) {
+ case Core::HID::NpadStyleIndex::ProController:
+ is_fusion_enabled = controller.sixaxis_fullkey.is_fusion_enabled;
+ break;
+ case Core::HID::NpadStyleIndex::Handheld:
+ is_fusion_enabled = controller.sixaxis_handheld.is_fusion_enabled;
+ break;
+ case Core::HID::NpadStyleIndex::JoyconDual:
+ case Core::HID::NpadStyleIndex::GameCube:
+ case Core::HID::NpadStyleIndex::Pokeball:
+ if (sixaxis_handle.device_index == Core::HID::DeviceIndex::Left) {
+ is_fusion_enabled = controller.sixaxis_dual_left.is_fusion_enabled;
+ break;
+ }
+ is_fusion_enabled = controller.sixaxis_dual_right.is_fusion_enabled;
+ break;
+ case Core::HID::NpadStyleIndex::JoyconLeft:
+ is_fusion_enabled = controller.sixaxis_left.is_fusion_enabled;
+ break;
+ case Core::HID::NpadStyleIndex::JoyconRight:
+ is_fusion_enabled = controller.sixaxis_right.is_fusion_enabled;
+ break;
+ default:
+ LOG_ERROR(Service_HID, "Invalid Npad type {}", sixaxis_handle.npad_type);
+ return NpadInvalidHandle;
+ }
-void Controller_NPad::SetSixAxisFusionParameters(
- Core::HID::SixAxisSensorHandle sixaxis_handle,
- Core::HID::SixAxisSensorFusionParameters sixaxis_fusion_parameters) {
+ return ResultSuccess;
+}
+ResultCode Controller_NPad::SetSixAxisFusionEnabled(Core::HID::SixAxisSensorHandle sixaxis_handle,
+ bool is_fusion_enabled) {
if (!IsDeviceHandleValid(sixaxis_handle)) {
LOG_ERROR(Service_HID, "Invalid handle");
- return;
+ return NpadInvalidHandle;
}
+
auto& controller = GetControllerFromHandle(sixaxis_handle);
- controller.sixaxis_fusion = sixaxis_fusion_parameters;
+ switch (sixaxis_handle.npad_type) {
+ case Core::HID::NpadStyleIndex::ProController:
+ controller.sixaxis_fullkey.is_fusion_enabled = is_fusion_enabled;
+ break;
+ case Core::HID::NpadStyleIndex::Handheld:
+ controller.sixaxis_handheld.is_fusion_enabled = is_fusion_enabled;
+ break;
+ case Core::HID::NpadStyleIndex::JoyconDual:
+ case Core::HID::NpadStyleIndex::GameCube:
+ case Core::HID::NpadStyleIndex::Pokeball:
+ if (sixaxis_handle.device_index == Core::HID::DeviceIndex::Left) {
+ controller.sixaxis_dual_left.is_fusion_enabled = is_fusion_enabled;
+ break;
+ }
+ controller.sixaxis_dual_right.is_fusion_enabled = is_fusion_enabled;
+ break;
+ case Core::HID::NpadStyleIndex::JoyconLeft:
+ controller.sixaxis_left.is_fusion_enabled = is_fusion_enabled;
+ break;
+ case Core::HID::NpadStyleIndex::JoyconRight:
+ controller.sixaxis_right.is_fusion_enabled = is_fusion_enabled;
+ break;
+ default:
+ LOG_ERROR(Service_HID, "Invalid Npad type {}", sixaxis_handle.npad_type);
+ return NpadInvalidHandle;
+ }
+
+ return ResultSuccess;
}
-Core::HID::SixAxisSensorFusionParameters Controller_NPad::GetSixAxisFusionParameters(
- Core::HID::SixAxisSensorHandle sixaxis_handle) {
+ResultCode Controller_NPad::SetSixAxisFusionParameters(
+ Core::HID::SixAxisSensorHandle sixaxis_handle,
+ Core::HID::SixAxisSensorFusionParameters sixaxis_fusion_parameters) {
if (!IsDeviceHandleValid(sixaxis_handle)) {
LOG_ERROR(Service_HID, "Invalid handle");
- // Since these parameters are unknow just return zeros
- return {};
+ return NpadInvalidHandle;
+ }
+ const auto param1 = sixaxis_fusion_parameters.parameter1;
+ if (param1 < 0.0f || param1 > 1.0f) {
+ return InvalidSixAxisFusionRange;
}
+
auto& controller = GetControllerFromHandle(sixaxis_handle);
- return controller.sixaxis_fusion;
+ switch (sixaxis_handle.npad_type) {
+ case Core::HID::NpadStyleIndex::ProController:
+ controller.sixaxis_fullkey.fusion = sixaxis_fusion_parameters;
+ break;
+ case Core::HID::NpadStyleIndex::Handheld:
+ controller.sixaxis_handheld.fusion = sixaxis_fusion_parameters;
+ break;
+ case Core::HID::NpadStyleIndex::JoyconDual:
+ case Core::HID::NpadStyleIndex::GameCube:
+ case Core::HID::NpadStyleIndex::Pokeball:
+ if (sixaxis_handle.device_index == Core::HID::DeviceIndex::Left) {
+ controller.sixaxis_dual_left.fusion = sixaxis_fusion_parameters;
+ break;
+ }
+ controller.sixaxis_dual_right.fusion = sixaxis_fusion_parameters;
+ break;
+ case Core::HID::NpadStyleIndex::JoyconLeft:
+ controller.sixaxis_left.fusion = sixaxis_fusion_parameters;
+ break;
+ case Core::HID::NpadStyleIndex::JoyconRight:
+ controller.sixaxis_right.fusion = sixaxis_fusion_parameters;
+ break;
+ default:
+ LOG_ERROR(Service_HID, "Invalid Npad type {}", sixaxis_handle.npad_type);
+ return NpadInvalidHandle;
+ }
+
+ return ResultSuccess;
}
-void Controller_NPad::ResetSixAxisFusionParameters(Core::HID::SixAxisSensorHandle sixaxis_handle) {
+ResultCode Controller_NPad::GetSixAxisFusionParameters(
+ Core::HID::SixAxisSensorHandle sixaxis_handle,
+ Core::HID::SixAxisSensorFusionParameters& parameters) const {
if (!IsDeviceHandleValid(sixaxis_handle)) {
LOG_ERROR(Service_HID, "Invalid handle");
- return;
+ return NpadInvalidHandle;
}
- auto& controller = GetControllerFromHandle(sixaxis_handle);
- // Since these parameters are unknow just fill with zeros
- controller.sixaxis_fusion = {};
+
+ const auto& controller = GetControllerFromHandle(sixaxis_handle);
+ switch (sixaxis_handle.npad_type) {
+ case Core::HID::NpadStyleIndex::ProController:
+ parameters = controller.sixaxis_fullkey.fusion;
+ break;
+ case Core::HID::NpadStyleIndex::Handheld:
+ parameters = controller.sixaxis_handheld.fusion;
+ break;
+ case Core::HID::NpadStyleIndex::JoyconDual:
+ case Core::HID::NpadStyleIndex::GameCube:
+ case Core::HID::NpadStyleIndex::Pokeball:
+ if (sixaxis_handle.device_index == Core::HID::DeviceIndex::Left) {
+ parameters = controller.sixaxis_dual_left.fusion;
+ break;
+ }
+ parameters = controller.sixaxis_dual_right.fusion;
+ break;
+ case Core::HID::NpadStyleIndex::JoyconLeft:
+ parameters = controller.sixaxis_left.fusion;
+ break;
+ case Core::HID::NpadStyleIndex::JoyconRight:
+ parameters = controller.sixaxis_right.fusion;
+ break;
+ default:
+ LOG_ERROR(Service_HID, "Invalid Npad type {}", sixaxis_handle.npad_type);
+ return NpadInvalidHandle;
+ }
+
+ return ResultSuccess;
}
void Controller_NPad::MergeSingleJoyAsDualJoy(Core::HID::NpadIdType npad_id_1,
diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h
index 3287cf435..f7313711f 100644
--- a/src/core/hle/service/hid/controllers/npad.h
+++ b/src/core/hle/service/hid/controllers/npad.h
@@ -28,7 +28,9 @@ class KReadableEvent;
namespace Service::KernelHelpers {
class ServiceContext;
-}
+} // namespace Service::KernelHelpers
+
+union ResultCode;
namespace Service::HID {
@@ -143,20 +145,26 @@ public:
void DisconnectNpad(Core::HID::NpadIdType npad_id);
- void SetGyroscopeZeroDriftMode(Core::HID::SixAxisSensorHandle sixaxis_handle,
- GyroscopeZeroDriftMode drift_mode);
- GyroscopeZeroDriftMode GetGyroscopeZeroDriftMode(
- Core::HID::SixAxisSensorHandle sixaxis_handle) const;
- bool IsSixAxisSensorAtRest(Core::HID::SixAxisSensorHandle sixaxis_handle) const;
- void SetSixAxisEnabled(Core::HID::SixAxisSensorHandle sixaxis_handle, bool sixaxis_status);
- void SetSixAxisFusionEnabled(Core::HID::SixAxisSensorHandle sixaxis_handle,
- bool sixaxis_fusion_status);
- void SetSixAxisFusionParameters(
+ ResultCode SetGyroscopeZeroDriftMode(Core::HID::SixAxisSensorHandle sixaxis_handle,
+ GyroscopeZeroDriftMode drift_mode);
+ ResultCode GetGyroscopeZeroDriftMode(Core::HID::SixAxisSensorHandle sixaxis_handle,
+ GyroscopeZeroDriftMode& drift_mode) const;
+ ResultCode IsSixAxisSensorAtRest(Core::HID::SixAxisSensorHandle sixaxis_handle,
+ bool& is_at_rest) const;
+ ResultCode IsFirmwareUpdateAvailableForSixAxisSensor(
+ Core::HID::SixAxisSensorHandle sixaxis_handle, bool& is_firmware_available) const;
+ ResultCode SetSixAxisEnabled(Core::HID::SixAxisSensorHandle sixaxis_handle,
+ bool sixaxis_status);
+ ResultCode IsSixAxisSensorFusionEnabled(Core::HID::SixAxisSensorHandle sixaxis_handle,
+ bool& is_fusion_enabled) const;
+ ResultCode SetSixAxisFusionEnabled(Core::HID::SixAxisSensorHandle sixaxis_handle,
+ bool is_fusion_enabled);
+ ResultCode SetSixAxisFusionParameters(
Core::HID::SixAxisSensorHandle sixaxis_handle,
Core::HID::SixAxisSensorFusionParameters sixaxis_fusion_parameters);
- Core::HID::SixAxisSensorFusionParameters GetSixAxisFusionParameters(
- Core::HID::SixAxisSensorHandle sixaxis_handle);
- void ResetSixAxisFusionParameters(Core::HID::SixAxisSensorHandle sixaxis_handle);
+ ResultCode GetSixAxisFusionParameters(
+ Core::HID::SixAxisSensorHandle sixaxis_handle,
+ Core::HID::SixAxisSensorFusionParameters& parameters) const;
Core::HID::LedPattern GetLedPattern(Core::HID::NpadIdType npad_id);
bool IsUnintendedHomeButtonInputProtectionEnabled(Core::HID::NpadIdType npad_id) const;
void SetUnintendedHomeButtonInputProtectionEnabled(bool is_protection_enabled,
@@ -191,16 +199,16 @@ private:
// This is nn::hid::detail::NpadFullKeyColorState
struct NpadFullKeyColorState {
- ColorAttribute attribute;
- Core::HID::NpadControllerColor fullkey;
+ ColorAttribute attribute{ColorAttribute::NoController};
+ Core::HID::NpadControllerColor fullkey{};
};
static_assert(sizeof(NpadFullKeyColorState) == 0xC, "NpadFullKeyColorState is an invalid size");
// This is nn::hid::detail::NpadJoyColorState
struct NpadJoyColorState {
- ColorAttribute attribute;
- Core::HID::NpadControllerColor left;
- Core::HID::NpadControllerColor right;
+ ColorAttribute attribute{ColorAttribute::NoController};
+ Core::HID::NpadControllerColor left{};
+ Core::HID::NpadControllerColor right{};
};
static_assert(sizeof(NpadJoyColorState) == 0x14, "NpadJoyColorState is an invalid size");
@@ -226,11 +234,11 @@ private:
// This is nn::hid::NpadPalmaState
// This is nn::hid::NpadSystemExtState
struct NPadGenericState {
- s64_le sampling_number;
- Core::HID::NpadButtonState npad_buttons;
- Core::HID::AnalogStickState l_stick;
- Core::HID::AnalogStickState r_stick;
- NpadAttribute connection_status;
+ s64_le sampling_number{};
+ Core::HID::NpadButtonState npad_buttons{};
+ Core::HID::AnalogStickState l_stick{};
+ Core::HID::AnalogStickState r_stick{};
+ NpadAttribute connection_status{};
INSERT_PADDING_BYTES(4); // Reserved
};
static_assert(sizeof(NPadGenericState) == 0x28, "NPadGenericState is an invalid size");
@@ -253,7 +261,7 @@ private:
Common::Vec3f gyro{};
Common::Vec3f rotation{};
std::array<Common::Vec3f, 3> orientation{};
- SixAxisSensorAttribute attribute;
+ SixAxisSensorAttribute attribute{};
INSERT_PADDING_BYTES(4); // Reserved
};
static_assert(sizeof(SixAxisSensorState) == 0x60, "SixAxisSensorState is an invalid size");
@@ -325,11 +333,11 @@ private:
// This is nn::hid::detail::NfcXcdDeviceHandleStateImpl
struct NfcXcdDeviceHandleStateImpl {
- u64 handle;
- bool is_available;
- bool is_activated;
+ u64 handle{};
+ bool is_available{};
+ bool is_activated{};
INSERT_PADDING_BYTES(0x6); // Reserved
- u64 sampling_number;
+ u64 sampling_number{};
};
static_assert(sizeof(NfcXcdDeviceHandleStateImpl) == 0x18,
"NfcXcdDeviceHandleStateImpl is an invalid size");
@@ -366,8 +374,8 @@ private:
};
struct AppletFooterUi {
- AppletFooterUiAttributes attributes;
- AppletFooterUiType type;
+ AppletFooterUiAttributes attributes{};
+ AppletFooterUiType type{AppletFooterUiType::None};
INSERT_PADDING_BYTES(0x5B); // Reserved
};
static_assert(sizeof(AppletFooterUi) == 0x60, "AppletFooterUi is an invalid size");
@@ -404,41 +412,41 @@ private:
// This is nn::hid::detail::NpadInternalState
struct NpadInternalState {
- Core::HID::NpadStyleTag style_tag;
- NpadJoyAssignmentMode assignment_mode;
- NpadFullKeyColorState fullkey_color;
- NpadJoyColorState joycon_color;
- Lifo<NPadGenericState, hid_entry_count> fullkey_lifo;
- Lifo<NPadGenericState, hid_entry_count> handheld_lifo;
- Lifo<NPadGenericState, hid_entry_count> joy_dual_lifo;
- Lifo<NPadGenericState, hid_entry_count> joy_left_lifo;
- Lifo<NPadGenericState, hid_entry_count> joy_right_lifo;
- Lifo<NPadGenericState, hid_entry_count> palma_lifo;
- Lifo<NPadGenericState, hid_entry_count> system_ext_lifo;
- Lifo<SixAxisSensorState, hid_entry_count> sixaxis_fullkey_lifo;
- Lifo<SixAxisSensorState, hid_entry_count> sixaxis_handheld_lifo;
- Lifo<SixAxisSensorState, hid_entry_count> sixaxis_dual_left_lifo;
- Lifo<SixAxisSensorState, hid_entry_count> sixaxis_dual_right_lifo;
- Lifo<SixAxisSensorState, hid_entry_count> sixaxis_left_lifo;
- Lifo<SixAxisSensorState, hid_entry_count> sixaxis_right_lifo;
- DeviceType device_type;
+ Core::HID::NpadStyleTag style_tag{Core::HID::NpadStyleSet::None};
+ NpadJoyAssignmentMode assignment_mode{NpadJoyAssignmentMode::Dual};
+ NpadFullKeyColorState fullkey_color{};
+ NpadJoyColorState joycon_color{};
+ Lifo<NPadGenericState, hid_entry_count> fullkey_lifo{};
+ Lifo<NPadGenericState, hid_entry_count> handheld_lifo{};
+ Lifo<NPadGenericState, hid_entry_count> joy_dual_lifo{};
+ Lifo<NPadGenericState, hid_entry_count> joy_left_lifo{};
+ Lifo<NPadGenericState, hid_entry_count> joy_right_lifo{};
+ Lifo<NPadGenericState, hid_entry_count> palma_lifo{};
+ Lifo<NPadGenericState, hid_entry_count> system_ext_lifo{};
+ Lifo<SixAxisSensorState, hid_entry_count> sixaxis_fullkey_lifo{};
+ Lifo<SixAxisSensorState, hid_entry_count> sixaxis_handheld_lifo{};
+ Lifo<SixAxisSensorState, hid_entry_count> sixaxis_dual_left_lifo{};
+ Lifo<SixAxisSensorState, hid_entry_count> sixaxis_dual_right_lifo{};
+ Lifo<SixAxisSensorState, hid_entry_count> sixaxis_left_lifo{};
+ Lifo<SixAxisSensorState, hid_entry_count> sixaxis_right_lifo{};
+ DeviceType device_type{};
INSERT_PADDING_BYTES(0x4); // Reserved
- NPadSystemProperties system_properties;
- NpadSystemButtonProperties button_properties;
- Core::HID::NpadBatteryLevel battery_level_dual;
- Core::HID::NpadBatteryLevel battery_level_left;
- Core::HID::NpadBatteryLevel battery_level_right;
+ NPadSystemProperties system_properties{};
+ NpadSystemButtonProperties button_properties{};
+ Core::HID::NpadBatteryLevel battery_level_dual{};
+ Core::HID::NpadBatteryLevel battery_level_left{};
+ Core::HID::NpadBatteryLevel battery_level_right{};
union {
- Lifo<NfcXcdDeviceHandleStateImpl, 0x2> nfc_xcd_device_lifo{};
- AppletFooterUi applet_footer;
+ AppletFooterUi applet_footer{};
+ Lifo<NfcXcdDeviceHandleStateImpl, 0x2> nfc_xcd_device_lifo;
};
INSERT_PADDING_BYTES(0x20); // Unknown
- Lifo<NpadGcTriggerState, hid_entry_count> gc_trigger_lifo;
- NpadLarkType lark_type_l_and_main;
- NpadLarkType lark_type_r;
- NpadLuciaType lucia_type;
- NpadLagonType lagon_type;
- NpadLagerType lager_type;
+ Lifo<NpadGcTriggerState, hid_entry_count> gc_trigger_lifo{};
+ NpadLarkType lark_type_l_and_main{};
+ NpadLarkType lark_type_r{};
+ NpadLuciaType lucia_type{};
+ NpadLagonType lagon_type{};
+ NpadLagerType lager_type{};
// FW 13.x Investigate there is some sort of bitflag related to joycons
INSERT_PADDING_BYTES(0x4);
INSERT_PADDING_BYTES(0xc08); // Unknown
@@ -451,6 +459,12 @@ private:
std::chrono::steady_clock::time_point last_vibration_timepoint{};
};
+ struct SixaxisParameters {
+ bool is_fusion_enabled{true};
+ Core::HID::SixAxisSensorFusionParameters fusion{};
+ GyroscopeZeroDriftMode gyroscope_zero_drift_mode{GyroscopeZeroDriftMode::Standard};
+ };
+
struct NpadControllerData {
Core::HID::EmulatedController* device;
Kernel::KEvent* styleset_changed_event{};
@@ -467,9 +481,12 @@ private:
// Motion parameters
bool sixaxis_at_rest{true};
bool sixaxis_sensor_enabled{true};
- bool sixaxis_fusion_enabled{false};
- Core::HID::SixAxisSensorFusionParameters sixaxis_fusion{};
- GyroscopeZeroDriftMode gyroscope_zero_drift_mode{GyroscopeZeroDriftMode::Standard};
+ SixaxisParameters sixaxis_fullkey{};
+ SixaxisParameters sixaxis_handheld{};
+ SixaxisParameters sixaxis_dual_left{};
+ SixaxisParameters sixaxis_dual_right{};
+ SixaxisParameters sixaxis_left{};
+ SixaxisParameters sixaxis_right{};
// Current pad state
NPadGenericState npad_pad_state{};
@@ -481,6 +498,7 @@ private:
SixAxisSensorState sixaxis_dual_right_state{};
SixAxisSensorState sixaxis_left_lifo_state{};
SixAxisSensorState sixaxis_right_lifo_state{};
+
int callback_key;
};
@@ -511,7 +529,8 @@ private:
NpadHandheldActivationMode handheld_activation_mode{NpadHandheldActivationMode::Dual};
NpadCommunicationMode communication_mode{NpadCommunicationMode::Default};
bool permit_vibration_session_enabled{false};
- bool analog_stick_use_center_clamp{};
+ bool analog_stick_use_center_clamp{false};
bool is_in_lr_assignment_mode{false};
+ bool is_controller_initialized{false};
};
} // namespace Service::HID
diff --git a/src/core/hle/service/hid/errors.h b/src/core/hle/service/hid/errors.h
index 3583642e7..7c5fb3e52 100644
--- a/src/core/hle/service/hid/errors.h
+++ b/src/core/hle/service/hid/errors.h
@@ -8,6 +8,8 @@
namespace Service::HID {
-constexpr ResultCode ERR_NPAD_NOT_CONNECTED{ErrorModule::HID, 710};
+constexpr ResultCode NpadInvalidHandle{ErrorModule::HID, 100};
+constexpr ResultCode InvalidSixAxisFusionRange{ErrorModule::HID, 423};
+constexpr ResultCode NpadNotConnected{ErrorModule::HID, 710};
} // namespace Service::HID
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index d9202ea6c..baf21df62 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -16,6 +16,7 @@
#include "core/hle/kernel/kernel.h"
#include "core/hle/service/hid/errors.h"
#include "core/hle/service/hid/hid.h"
+#include "core/hle/service/hid/hidbus.h"
#include "core/hle/service/hid/irs.h"
#include "core/hle/service/hid/xcd.h"
#include "core/memory.h"
@@ -247,7 +248,7 @@ Hid::Hid(Core::System& system_)
{65, nullptr, "GetJoySixAxisSensorLifoHandle"},
{66, &Hid::StartSixAxisSensor, "StartSixAxisSensor"},
{67, &Hid::StopSixAxisSensor, "StopSixAxisSensor"},
- {68, nullptr, "IsSixAxisSensorFusionEnabled"},
+ {68, &Hid::IsSixAxisSensorFusionEnabled, "IsSixAxisSensorFusionEnabled"},
{69, &Hid::EnableSixAxisSensorFusion, "EnableSixAxisSensorFusion"},
{70, &Hid::SetSixAxisSensorFusionParameters, "SetSixAxisSensorFusionParameters"},
{71, &Hid::GetSixAxisSensorFusionParameters, "GetSixAxisSensorFusionParameters"},
@@ -527,8 +528,8 @@ void Hid::StartSixAxisSensor(Kernel::HLERequestContext& ctx) {
const auto parameters{rp.PopRaw<Parameters>()};
- applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .SetSixAxisEnabled(parameters.sixaxis_handle, true);
+ auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
+ const auto result = controller.SetSixAxisEnabled(parameters.sixaxis_handle, true);
LOG_DEBUG(Service_HID,
"called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
@@ -536,7 +537,7 @@ void Hid::StartSixAxisSensor(Kernel::HLERequestContext& ctx) {
parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ rb.Push(result);
}
void Hid::StopSixAxisSensor(Kernel::HLERequestContext& ctx) {
@@ -550,8 +551,8 @@ void Hid::StopSixAxisSensor(Kernel::HLERequestContext& ctx) {
const auto parameters{rp.PopRaw<Parameters>()};
- applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .SetSixAxisEnabled(parameters.sixaxis_handle, false);
+ auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
+ const auto result = controller.SetSixAxisEnabled(parameters.sixaxis_handle, false);
LOG_DEBUG(Service_HID,
"called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
@@ -559,7 +560,33 @@ void Hid::StopSixAxisSensor(Kernel::HLERequestContext& ctx) {
parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ rb.Push(result);
+}
+
+void Hid::IsSixAxisSensorFusionEnabled(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Core::HID::SixAxisSensorHandle sixaxis_handle;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ bool is_enabled{};
+ auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
+ const auto result =
+ controller.IsSixAxisSensorFusionEnabled(parameters.sixaxis_handle, is_enabled);
+
+ LOG_DEBUG(Service_HID,
+ "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
+ parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
+ parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(result);
+ rb.Push(is_enabled);
}
void Hid::EnableSixAxisSensorFusion(Kernel::HLERequestContext& ctx) {
@@ -574,9 +601,9 @@ void Hid::EnableSixAxisSensorFusion(Kernel::HLERequestContext& ctx) {
const auto parameters{rp.PopRaw<Parameters>()};
- applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .SetSixAxisFusionEnabled(parameters.sixaxis_handle,
- parameters.enable_sixaxis_sensor_fusion);
+ auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
+ const auto result = controller.SetSixAxisFusionEnabled(parameters.sixaxis_handle,
+ parameters.enable_sixaxis_sensor_fusion);
LOG_DEBUG(Service_HID,
"called, enable_sixaxis_sensor_fusion={}, npad_type={}, npad_id={}, "
@@ -586,7 +613,7 @@ void Hid::EnableSixAxisSensorFusion(Kernel::HLERequestContext& ctx) {
parameters.applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ rb.Push(result);
}
void Hid::SetSixAxisSensorFusionParameters(Kernel::HLERequestContext& ctx) {
@@ -601,8 +628,9 @@ void Hid::SetSixAxisSensorFusionParameters(Kernel::HLERequestContext& ctx) {
const auto parameters{rp.PopRaw<Parameters>()};
- applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .SetSixAxisFusionParameters(parameters.sixaxis_handle, parameters.sixaxis_fusion);
+ auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
+ const auto result =
+ controller.SetSixAxisFusionParameters(parameters.sixaxis_handle, parameters.sixaxis_fusion);
LOG_DEBUG(Service_HID,
"called, npad_type={}, npad_id={}, device_index={}, parameter1={}, "
@@ -612,7 +640,7 @@ void Hid::SetSixAxisSensorFusionParameters(Kernel::HLERequestContext& ctx) {
parameters.sixaxis_fusion.parameter2, parameters.applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ rb.Push(result);
}
void Hid::GetSixAxisSensorFusionParameters(Kernel::HLERequestContext& ctx) {
@@ -626,9 +654,11 @@ void Hid::GetSixAxisSensorFusionParameters(Kernel::HLERequestContext& ctx) {
const auto parameters{rp.PopRaw<Parameters>()};
- const auto sixaxis_fusion_parameters =
- applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .GetSixAxisFusionParameters(parameters.sixaxis_handle);
+ Core::HID::SixAxisSensorFusionParameters fusion_parameters{};
+ const auto& controller =
+ GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
+ const auto result =
+ controller.GetSixAxisFusionParameters(parameters.sixaxis_handle, fusion_parameters);
LOG_DEBUG(Service_HID,
"called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
@@ -636,8 +666,8 @@ void Hid::GetSixAxisSensorFusionParameters(Kernel::HLERequestContext& ctx) {
parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 4};
- rb.Push(ResultSuccess);
- rb.PushRaw(sixaxis_fusion_parameters);
+ rb.Push(result);
+ rb.PushRaw(fusion_parameters);
}
void Hid::ResetSixAxisSensorFusionParameters(Kernel::HLERequestContext& ctx) {
@@ -651,8 +681,15 @@ void Hid::ResetSixAxisSensorFusionParameters(Kernel::HLERequestContext& ctx) {
const auto parameters{rp.PopRaw<Parameters>()};
- applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .ResetSixAxisFusionParameters(parameters.sixaxis_handle);
+ // Since these parameters are unknow just use what HW outputs
+ const Core::HID::SixAxisSensorFusionParameters fusion_parameters{
+ .parameter1 = 0.03f,
+ .parameter2 = 0.4f,
+ };
+ auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
+ const auto result1 =
+ controller.SetSixAxisFusionParameters(parameters.sixaxis_handle, fusion_parameters);
+ const auto result2 = controller.SetSixAxisFusionEnabled(parameters.sixaxis_handle, true);
LOG_DEBUG(Service_HID,
"called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
@@ -660,6 +697,14 @@ void Hid::ResetSixAxisSensorFusionParameters(Kernel::HLERequestContext& ctx) {
parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
+ if (result1.IsError()) {
+ rb.Push(result1);
+ return;
+ }
+ if (result2.IsError()) {
+ rb.Push(result2);
+ return;
+ }
rb.Push(ResultSuccess);
}
@@ -669,8 +714,8 @@ void Hid::SetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) {
const auto drift_mode{rp.PopEnum<Controller_NPad::GyroscopeZeroDriftMode>()};
const auto applet_resource_user_id{rp.Pop<u64>()};
- applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .SetGyroscopeZeroDriftMode(sixaxis_handle, drift_mode);
+ auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
+ const auto result = controller.SetGyroscopeZeroDriftMode(sixaxis_handle, drift_mode);
LOG_DEBUG(Service_HID,
"called, npad_type={}, npad_id={}, device_index={}, drift_mode={}, "
@@ -679,7 +724,7 @@ void Hid::SetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) {
drift_mode, applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ rb.Push(result);
}
void Hid::GetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) {
@@ -693,15 +738,18 @@ void Hid::GetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) {
const auto parameters{rp.PopRaw<Parameters>()};
+ auto drift_mode{Controller_NPad::GyroscopeZeroDriftMode::Standard};
+ auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
+ const auto result = controller.GetGyroscopeZeroDriftMode(parameters.sixaxis_handle, drift_mode);
+
LOG_DEBUG(Service_HID,
"called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.PushEnum(applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .GetGyroscopeZeroDriftMode(parameters.sixaxis_handle));
+ rb.Push(result);
+ rb.PushEnum(drift_mode);
}
void Hid::ResetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) {
@@ -714,10 +762,10 @@ void Hid::ResetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) {
static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
const auto parameters{rp.PopRaw<Parameters>()};
- const auto drift_mode{Controller_NPad::GyroscopeZeroDriftMode::Standard};
- applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .SetGyroscopeZeroDriftMode(parameters.sixaxis_handle, drift_mode);
+ const auto drift_mode{Controller_NPad::GyroscopeZeroDriftMode::Standard};
+ auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
+ const auto result = controller.SetGyroscopeZeroDriftMode(parameters.sixaxis_handle, drift_mode);
LOG_DEBUG(Service_HID,
"called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
@@ -725,7 +773,7 @@ void Hid::ResetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) {
parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ rb.Push(result);
}
void Hid::IsSixAxisSensorAtRest(Kernel::HLERequestContext& ctx) {
@@ -739,15 +787,18 @@ void Hid::IsSixAxisSensorAtRest(Kernel::HLERequestContext& ctx) {
const auto parameters{rp.PopRaw<Parameters>()};
+ bool is_at_rest{};
+ auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
+ const auto result = controller.IsSixAxisSensorAtRest(parameters.sixaxis_handle, is_at_rest);
+
LOG_DEBUG(Service_HID,
"called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.Push(applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .IsSixAxisSensorAtRest(parameters.sixaxis_handle));
+ rb.Push(result);
+ rb.Push(is_at_rest);
}
void Hid::IsFirmwareUpdateAvailableForSixAxisSensor(Kernel::HLERequestContext& ctx) {
@@ -761,6 +812,11 @@ void Hid::IsFirmwareUpdateAvailableForSixAxisSensor(Kernel::HLERequestContext& c
const auto parameters{rp.PopRaw<Parameters>()};
+ bool is_firmware_available{};
+ auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
+ const auto result = controller.IsFirmwareUpdateAvailableForSixAxisSensor(
+ parameters.sixaxis_handle, is_firmware_available);
+
LOG_WARNING(
Service_HID,
"(STUBBED) called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
@@ -768,8 +824,8 @@ void Hid::IsFirmwareUpdateAvailableForSixAxisSensor(Kernel::HLERequestContext& c
parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.Push(false);
+ rb.Push(result);
+ rb.Push(is_firmware_available);
}
void Hid::ActivateGesture(Kernel::HLERequestContext& ctx) {
@@ -878,6 +934,10 @@ void Hid::AcquireNpadStyleSetUpdateEventHandle(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}, unknown={}",
parameters.npad_id, parameters.applet_resource_user_id, parameters.unknown);
+ // Games expect this event to be signaled after calling this function
+ applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .SignalStyleSetChangedEvent(parameters.npad_id);
+
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(ResultSuccess);
rb.PushCopyObjects(applet_resource->GetController<Controller_NPad>(HidController::NPad)
@@ -1115,7 +1175,7 @@ void Hid::SwapNpadAssignment(Kernel::HLERequestContext& ctx) {
rb.Push(ResultSuccess);
} else {
LOG_ERROR(Service_HID, "Npads are not connected!");
- rb.Push(ERR_NPAD_NOT_CONNECTED);
+ rb.Push(NpadNotConnected);
}
}
@@ -2124,32 +2184,6 @@ public:
}
};
-class HidBus final : public ServiceFramework<HidBus> {
-public:
- explicit HidBus(Core::System& system_) : ServiceFramework{system_, "hidbus"} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {1, nullptr, "GetBusHandle"},
- {2, nullptr, "IsExternalDeviceConnected"},
- {3, nullptr, "Initialize"},
- {4, nullptr, "Finalize"},
- {5, nullptr, "EnableExternalDevice"},
- {6, nullptr, "GetExternalDeviceId"},
- {7, nullptr, "SendCommandAsync"},
- {8, nullptr, "GetSendCommandAsynceResult"},
- {9, nullptr, "SetEventForSendCommandAsycResult"},
- {10, nullptr, "GetSharedMemoryHandle"},
- {11, nullptr, "EnableJoyPollingReceiveMode"},
- {12, nullptr, "DisableJoyPollingReceiveMode"},
- {13, nullptr, "GetPollingData"},
- {14, nullptr, "SetStatusManagerType"},
- };
- // clang-format on
-
- RegisterHandlers(functions);
- }
-};
-
void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {
std::make_shared<Hid>(system)->InstallAsService(service_manager);
std::make_shared<HidBus>(system)->InstallAsService(service_manager);
diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h
index c281081a7..666cb6ff5 100644
--- a/src/core/hle/service/hid/hid.h
+++ b/src/core/hle/service/hid/hid.h
@@ -103,6 +103,7 @@ private:
void DeactivateSixAxisSensor(Kernel::HLERequestContext& ctx);
void StartSixAxisSensor(Kernel::HLERequestContext& ctx);
void StopSixAxisSensor(Kernel::HLERequestContext& ctx);
+ void IsSixAxisSensorFusionEnabled(Kernel::HLERequestContext& ctx);
void EnableSixAxisSensorFusion(Kernel::HLERequestContext& ctx);
void SetSixAxisSensorFusionParameters(Kernel::HLERequestContext& ctx);
void GetSixAxisSensorFusionParameters(Kernel::HLERequestContext& ctx);
diff --git a/src/core/hle/service/hid/hidbus.cpp b/src/core/hle/service/hid/hidbus.cpp
new file mode 100644
index 000000000..af7662a15
--- /dev/null
+++ b/src/core/hle/service/hid/hidbus.cpp
@@ -0,0 +1,531 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/logging/log.h"
+#include "common/settings.h"
+#include "core/core.h"
+#include "core/core_timing.h"
+#include "core/core_timing_util.h"
+#include "core/hid/hid_types.h"
+#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/kernel/k_readable_event.h"
+#include "core/hle/kernel/k_shared_memory.h"
+#include "core/hle/kernel/k_transfer_memory.h"
+#include "core/hle/service/hid/hidbus.h"
+#include "core/hle/service/hid/hidbus/ringcon.h"
+#include "core/hle/service/hid/hidbus/starlink.h"
+#include "core/hle/service/hid/hidbus/stubbed.h"
+#include "core/hle/service/service.h"
+#include "core/memory.h"
+
+namespace Service::HID {
+// (15ms, 66Hz)
+constexpr auto hidbus_update_ns = std::chrono::nanoseconds{15 * 1000 * 1000};
+
+HidBus::HidBus(Core::System& system_)
+ : ServiceFramework{system_, "hidbus"}, service_context{system_, service_name} {
+
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {1, &HidBus::GetBusHandle, "GetBusHandle"},
+ {2, &HidBus::IsExternalDeviceConnected, "IsExternalDeviceConnected"},
+ {3, &HidBus::Initialize, "Initialize"},
+ {4, &HidBus::Finalize, "Finalize"},
+ {5, &HidBus::EnableExternalDevice, "EnableExternalDevice"},
+ {6, &HidBus::GetExternalDeviceId, "GetExternalDeviceId"},
+ {7, &HidBus::SendCommandAsync, "SendCommandAsync"},
+ {8, &HidBus::GetSendCommandAsynceResult, "GetSendCommandAsynceResult"},
+ {9, &HidBus::SetEventForSendCommandAsycResult, "SetEventForSendCommandAsycResult"},
+ {10, &HidBus::GetSharedMemoryHandle, "GetSharedMemoryHandle"},
+ {11, &HidBus::EnableJoyPollingReceiveMode, "EnableJoyPollingReceiveMode"},
+ {12, &HidBus::DisableJoyPollingReceiveMode, "DisableJoyPollingReceiveMode"},
+ {13, nullptr, "GetPollingData"},
+ {14, &HidBus::SetStatusManagerType, "SetStatusManagerType"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+
+ // Register update callbacks
+ hidbus_update_event = Core::Timing::CreateEvent(
+ "Hidbus::UpdateCallback",
+ [this](std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
+ const auto guard = LockService();
+ UpdateHidbus(user_data, ns_late);
+ });
+
+ system_.CoreTiming().ScheduleEvent(hidbus_update_ns, hidbus_update_event);
+}
+
+HidBus::~HidBus() {
+ system.CoreTiming().UnscheduleEvent(hidbus_update_event, 0);
+}
+
+void HidBus::UpdateHidbus(std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
+ auto& core_timing = system.CoreTiming();
+
+ if (is_hidbus_enabled) {
+ for (std::size_t i = 0; i < devices.size(); ++i) {
+ if (!devices[i].is_device_initializated) {
+ continue;
+ }
+ auto& device = devices[i].device;
+ device->OnUpdate();
+ auto& cur_entry = hidbus_status.entries[devices[i].handle.internal_index];
+ cur_entry.is_polling_mode = device->IsPollingMode();
+ cur_entry.polling_mode = device->GetPollingMode();
+ cur_entry.is_enabled = device->IsEnabled();
+
+ u8* shared_memory = system.Kernel().GetHidBusSharedMem().GetPointer();
+ std::memcpy(shared_memory + (i * sizeof(HidbusStatusManagerEntry)), &hidbus_status,
+ sizeof(HidbusStatusManagerEntry));
+ }
+ }
+
+ // If ns_late is higher than the update rate ignore the delay
+ if (ns_late > hidbus_update_ns) {
+ ns_late = {};
+ }
+
+ core_timing.ScheduleEvent(hidbus_update_ns - ns_late, hidbus_update_event);
+}
+
+std::optional<std::size_t> HidBus::GetDeviceIndexFromHandle(BusHandle handle) const {
+ for (std::size_t i = 0; i < devices.size(); ++i) {
+ const auto& device_handle = devices[i].handle;
+ if (handle.abstracted_pad_id == device_handle.abstracted_pad_id &&
+ handle.internal_index == device_handle.internal_index &&
+ handle.player_number == device_handle.player_number &&
+ handle.bus_type == device_handle.bus_type &&
+ handle.is_valid == device_handle.is_valid) {
+ return i;
+ }
+ }
+ return std::nullopt;
+}
+
+void HidBus::GetBusHandle(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Core::HID::NpadIdType npad_id;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ BusType bus_type;
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x18, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ LOG_INFO(Service_HID, "called, npad_id={}, bus_type={}, applet_resource_user_id={}",
+ parameters.npad_id, parameters.bus_type, parameters.applet_resource_user_id);
+
+ bool is_handle_found = 0;
+ std::size_t handle_index = 0;
+
+ for (std::size_t i = 0; i < devices.size(); i++) {
+ const auto& handle = devices[i].handle;
+ if (!handle.is_valid) {
+ continue;
+ }
+ if (static_cast<Core::HID::NpadIdType>(handle.player_number) == parameters.npad_id &&
+ handle.bus_type == parameters.bus_type) {
+ is_handle_found = true;
+ handle_index = i;
+ break;
+ }
+ }
+
+ // Handle not found. Create a new one
+ if (!is_handle_found) {
+ for (std::size_t i = 0; i < devices.size(); i++) {
+ if (devices[i].handle.is_valid) {
+ continue;
+ }
+ devices[i].handle = {
+ .abstracted_pad_id = static_cast<u8>(i),
+ .internal_index = static_cast<u8>(i),
+ .player_number = static_cast<u8>(parameters.npad_id),
+ .bus_type = parameters.bus_type,
+ .is_valid = true,
+ };
+ handle_index = i;
+ break;
+ }
+ }
+
+ struct OutData {
+ bool is_valid;
+ INSERT_PADDING_BYTES(7);
+ BusHandle handle;
+ };
+ static_assert(sizeof(OutData) == 0x10, "OutData has incorrect size.");
+
+ const OutData out_data{
+ .is_valid = true,
+ .handle = devices[handle_index].handle,
+ };
+
+ IPC::ResponseBuilder rb{ctx, 6};
+ rb.Push(ResultSuccess);
+ rb.PushRaw(out_data);
+}
+
+void HidBus::IsExternalDeviceConnected(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto bus_handle_{rp.PopRaw<BusHandle>()};
+
+ LOG_INFO(Service_HID,
+ "Called, abstracted_pad_id={}, bus_type={}, internal_index={}, "
+ "player_number={}, is_valid={}",
+ bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index,
+ bus_handle_.player_number, bus_handle_.is_valid);
+
+ const auto device_index = GetDeviceIndexFromHandle(bus_handle_);
+
+ if (device_index) {
+ const auto& device = devices[device_index.value()].device;
+ const bool is_attached = device->IsDeviceActivated();
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(is_attached);
+ return;
+ }
+
+ LOG_ERROR(Service_HID, "Invalid handle");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultUnknown);
+ return;
+}
+
+void HidBus::Initialize(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto bus_handle_{rp.PopRaw<BusHandle>()};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_INFO(Service_HID,
+ "called, abstracted_pad_id={} bus_type={} internal_index={} "
+ "player_number={} is_valid={}, applet_resource_user_id={}",
+ bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index,
+ bus_handle_.player_number, bus_handle_.is_valid, applet_resource_user_id);
+
+ is_hidbus_enabled = true;
+
+ const auto device_index = GetDeviceIndexFromHandle(bus_handle_);
+
+ if (device_index) {
+ const auto entry_index = devices[device_index.value()].handle.internal_index;
+ auto& cur_entry = hidbus_status.entries[entry_index];
+
+ if (bus_handle_.internal_index == 0 && Settings::values.enable_ring_controller) {
+ MakeDevice<RingController>(bus_handle_);
+ devices[device_index.value()].is_device_initializated = true;
+ devices[device_index.value()].device->ActivateDevice();
+ cur_entry.is_in_focus = true;
+ cur_entry.is_connected = true;
+ cur_entry.is_connected_result = ResultSuccess;
+ cur_entry.is_enabled = false;
+ cur_entry.is_polling_mode = false;
+ } else {
+ MakeDevice<HidbusStubbed>(bus_handle_);
+ devices[device_index.value()].is_device_initializated = true;
+ cur_entry.is_in_focus = true;
+ cur_entry.is_connected = false;
+ cur_entry.is_connected_result = ResultSuccess;
+ cur_entry.is_enabled = false;
+ cur_entry.is_polling_mode = false;
+ }
+
+ std::memcpy(system.Kernel().GetHidBusSharedMem().GetPointer(), &hidbus_status,
+ sizeof(hidbus_status));
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+ return;
+ }
+
+ LOG_ERROR(Service_HID, "Invalid handle");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultUnknown);
+ return;
+}
+
+void HidBus::Finalize(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto bus_handle_{rp.PopRaw<BusHandle>()};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_INFO(Service_HID,
+ "called, abstracted_pad_id={}, bus_type={}, internal_index={}, "
+ "player_number={}, is_valid={}, applet_resource_user_id={}",
+ bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index,
+ bus_handle_.player_number, bus_handle_.is_valid, applet_resource_user_id);
+
+ const auto device_index = GetDeviceIndexFromHandle(bus_handle_);
+
+ if (device_index) {
+ const auto entry_index = devices[device_index.value()].handle.internal_index;
+ auto& cur_entry = hidbus_status.entries[entry_index];
+ auto& device = devices[device_index.value()].device;
+ devices[device_index.value()].is_device_initializated = false;
+ device->DeactivateDevice();
+
+ cur_entry.is_in_focus = true;
+ cur_entry.is_connected = false;
+ cur_entry.is_connected_result = ResultSuccess;
+ cur_entry.is_enabled = false;
+ cur_entry.is_polling_mode = false;
+ std::memcpy(system.Kernel().GetHidBusSharedMem().GetPointer(), &hidbus_status,
+ sizeof(hidbus_status));
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+ return;
+ }
+
+ LOG_ERROR(Service_HID, "Invalid handle");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultUnknown);
+ return;
+}
+
+void HidBus::EnableExternalDevice(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ bool enable;
+ INSERT_PADDING_BYTES_NOINIT(7);
+ BusHandle bus_handle;
+ u64 inval;
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x20, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ LOG_INFO(Service_HID,
+ "called, enable={}, abstracted_pad_id={}, bus_type={}, internal_index={}, "
+ "player_number={}, is_valid={}, inval={}, applet_resource_user_id{}",
+ parameters.enable, parameters.bus_handle.abstracted_pad_id,
+ parameters.bus_handle.bus_type, parameters.bus_handle.internal_index,
+ parameters.bus_handle.player_number, parameters.bus_handle.is_valid, parameters.inval,
+ parameters.applet_resource_user_id);
+
+ const auto device_index = GetDeviceIndexFromHandle(parameters.bus_handle);
+
+ if (device_index) {
+ auto& device = devices[device_index.value()].device;
+ device->Enable(parameters.enable);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+ return;
+ }
+
+ LOG_ERROR(Service_HID, "Invalid handle");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultUnknown);
+ return;
+}
+
+void HidBus::GetExternalDeviceId(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto bus_handle_{rp.PopRaw<BusHandle>()};
+
+ LOG_INFO(Service_HID,
+ "called, abstracted_pad_id={}, bus_type={}, internal_index={}, player_number={}, "
+ "is_valid={}",
+ bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index,
+ bus_handle_.player_number, bus_handle_.is_valid);
+
+ const auto device_index = GetDeviceIndexFromHandle(bus_handle_);
+
+ if (device_index) {
+ const auto& device = devices[device_index.value()].device;
+ u32 device_id = device->GetDeviceId();
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push<u32>(device_id);
+ return;
+ }
+
+ LOG_ERROR(Service_HID, "Invalid handle");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultUnknown);
+ return;
+}
+
+void HidBus::SendCommandAsync(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto data = ctx.ReadBuffer();
+ const auto bus_handle_{rp.PopRaw<BusHandle>()};
+
+ LOG_DEBUG(Service_HID,
+ "called, data_size={}, abstracted_pad_id={}, bus_type={}, internal_index={}, "
+ "player_number={}, is_valid={}",
+ data.size(), bus_handle_.abstracted_pad_id, bus_handle_.bus_type,
+ bus_handle_.internal_index, bus_handle_.player_number, bus_handle_.is_valid);
+
+ const auto device_index = GetDeviceIndexFromHandle(bus_handle_);
+
+ if (device_index) {
+ auto& device = devices[device_index.value()].device;
+ device->SetCommand(data);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+ return;
+ }
+
+ LOG_ERROR(Service_HID, "Invalid handle");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultUnknown);
+ return;
+};
+
+void HidBus::GetSendCommandAsynceResult(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto bus_handle_{rp.PopRaw<BusHandle>()};
+
+ LOG_DEBUG(Service_HID,
+ "called, abstracted_pad_id={}, bus_type={}, internal_index={}, player_number={}, "
+ "is_valid={}",
+ bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index,
+ bus_handle_.player_number, bus_handle_.is_valid);
+
+ const auto device_index = GetDeviceIndexFromHandle(bus_handle_);
+
+ if (device_index) {
+ const auto& device = devices[device_index.value()].device;
+ const std::vector<u8> data = device->GetReply();
+ const u64 data_size = ctx.WriteBuffer(data);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(ResultSuccess);
+ rb.Push<u64>(data_size);
+ return;
+ }
+
+ LOG_ERROR(Service_HID, "Invalid handle");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultUnknown);
+ return;
+};
+
+void HidBus::SetEventForSendCommandAsycResult(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto bus_handle_{rp.PopRaw<BusHandle>()};
+
+ LOG_INFO(Service_HID,
+ "called, abstracted_pad_id={}, bus_type={}, internal_index={}, player_number={}, "
+ "is_valid={}",
+ bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index,
+ bus_handle_.player_number, bus_handle_.is_valid);
+
+ const auto device_index = GetDeviceIndexFromHandle(bus_handle_);
+
+ if (device_index) {
+ const auto& device = devices[device_index.value()].device;
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(ResultSuccess);
+ rb.PushCopyObjects(device->GetSendCommandAsycEvent());
+ return;
+ }
+
+ LOG_ERROR(Service_HID, "Invalid handle");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultUnknown);
+ return;
+};
+
+void HidBus::GetSharedMemoryHandle(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_HID, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(ResultSuccess);
+ rb.PushCopyObjects(&system.Kernel().GetHidBusSharedMem());
+}
+
+void HidBus::EnableJoyPollingReceiveMode(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto t_mem_size{rp.Pop<u32>()};
+ const auto t_mem_handle{ctx.GetCopyHandle(0)};
+ const auto polling_mode_{rp.PopEnum<JoyPollingMode>()};
+ const auto bus_handle_{rp.PopRaw<BusHandle>()};
+
+ ASSERT_MSG(t_mem_size == 0x1000, "t_mem_size is not 0x1000 bytes");
+
+ auto t_mem =
+ system.CurrentProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(t_mem_handle);
+
+ if (t_mem.IsNull()) {
+ LOG_ERROR(Service_HID, "t_mem is a nullptr for handle=0x{:08X}", t_mem_handle);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultUnknown);
+ return;
+ }
+
+ ASSERT_MSG(t_mem->GetSize() == 0x1000, "t_mem has incorrect size");
+
+ LOG_INFO(Service_HID,
+ "called, t_mem_handle=0x{:08X}, polling_mode={}, abstracted_pad_id={}, bus_type={}, "
+ "internal_index={}, player_number={}, is_valid={}",
+ t_mem_handle, polling_mode_, bus_handle_.abstracted_pad_id, bus_handle_.bus_type,
+ bus_handle_.internal_index, bus_handle_.player_number, bus_handle_.is_valid);
+
+ const auto device_index = GetDeviceIndexFromHandle(bus_handle_);
+
+ if (device_index) {
+ auto& device = devices[device_index.value()].device;
+ device->SetPollingMode(polling_mode_);
+ device->SetTransferMemoryPointer(system.Memory().GetPointer(t_mem->GetSourceAddress()));
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+ return;
+ }
+
+ LOG_ERROR(Service_HID, "Invalid handle");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultUnknown);
+ return;
+}
+
+void HidBus::DisableJoyPollingReceiveMode(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto bus_handle_{rp.PopRaw<BusHandle>()};
+
+ LOG_INFO(Service_HID,
+ "called, abstracted_pad_id={}, bus_type={}, internal_index={}, player_number={}, "
+ "is_valid={}",
+ bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index,
+ bus_handle_.player_number, bus_handle_.is_valid);
+
+ const auto device_index = GetDeviceIndexFromHandle(bus_handle_);
+
+ if (device_index) {
+ auto& device = devices[device_index.value()].device;
+ device->DisablePollingMode();
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+ return;
+ }
+
+ LOG_ERROR(Service_HID, "Invalid handle");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultUnknown);
+ return;
+}
+
+void HidBus::SetStatusManagerType(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto manager_type{rp.PopEnum<StatusManagerType>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, manager_type={}", manager_type);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+};
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/hidbus.h b/src/core/hle/service/hid/hidbus.h
new file mode 100644
index 000000000..b10d5156a
--- /dev/null
+++ b/src/core/hle/service/hid/hidbus.h
@@ -0,0 +1,131 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <functional>
+
+#include "core/hle/service/hid/hidbus/hidbus_base.h"
+#include "core/hle/service/kernel_helpers.h"
+#include "core/hle/service/service.h"
+
+namespace Core::Timing {
+struct EventType;
+} // namespace Core::Timing
+
+namespace Core {
+class System;
+} // namespace Core
+
+namespace Service::HID {
+
+class HidBus final : public ServiceFramework<HidBus> {
+public:
+ explicit HidBus(Core::System& system_);
+ ~HidBus() override;
+
+private:
+ static const std::size_t max_number_of_handles = 0x13;
+
+ enum class HidBusDeviceId : std::size_t {
+ RingController = 0x20,
+ FamicomRight = 0x21,
+ Starlink = 0x28,
+ };
+
+ // This is nn::hidbus::detail::StatusManagerType
+ enum class StatusManagerType : u32 {
+ None,
+ Type16,
+ Type32,
+ };
+
+ // This is nn::hidbus::BusType
+ enum class BusType : u8 {
+ LeftJoyRail,
+ RightJoyRail,
+ InternalBus, // Lark microphone
+
+ MaxBusType,
+ };
+
+ // This is nn::hidbus::BusHandle
+ struct BusHandle {
+ u32 abstracted_pad_id;
+ u8 internal_index;
+ u8 player_number;
+ BusType bus_type;
+ bool is_valid;
+ };
+ static_assert(sizeof(BusHandle) == 0x8, "BusHandle is an invalid size");
+
+ // This is nn::hidbus::JoyPollingReceivedData
+ struct JoyPollingReceivedData {
+ std::array<u8, 0x30> data;
+ u64 out_size;
+ u64 sampling_number;
+ };
+ static_assert(sizeof(JoyPollingReceivedData) == 0x40,
+ "JoyPollingReceivedData is an invalid size");
+
+ struct HidbusStatusManagerEntry {
+ u8 is_connected{};
+ INSERT_PADDING_BYTES(0x3);
+ ResultCode is_connected_result{0};
+ u8 is_enabled{};
+ u8 is_in_focus{};
+ u8 is_polling_mode{};
+ u8 reserved{};
+ JoyPollingMode polling_mode{};
+ INSERT_PADDING_BYTES(0x70); // Unknown
+ };
+ static_assert(sizeof(HidbusStatusManagerEntry) == 0x80,
+ "HidbusStatusManagerEntry is an invalid size");
+
+ struct HidbusStatusManager {
+ std::array<HidbusStatusManagerEntry, max_number_of_handles> entries{};
+ INSERT_PADDING_BYTES(0x680); // Unused
+ };
+ static_assert(sizeof(HidbusStatusManager) <= 0x1000, "HidbusStatusManager is an invalid size");
+
+ struct HidbusDevice {
+ bool is_device_initializated{};
+ BusHandle handle{};
+ std::unique_ptr<HidbusBase> device{nullptr};
+ };
+
+ void GetBusHandle(Kernel::HLERequestContext& ctx);
+ void IsExternalDeviceConnected(Kernel::HLERequestContext& ctx);
+ void Initialize(Kernel::HLERequestContext& ctx);
+ void Finalize(Kernel::HLERequestContext& ctx);
+ void EnableExternalDevice(Kernel::HLERequestContext& ctx);
+ void GetExternalDeviceId(Kernel::HLERequestContext& ctx);
+ void SendCommandAsync(Kernel::HLERequestContext& ctx);
+ void GetSendCommandAsynceResult(Kernel::HLERequestContext& ctx);
+ void SetEventForSendCommandAsycResult(Kernel::HLERequestContext& ctx);
+ void GetSharedMemoryHandle(Kernel::HLERequestContext& ctx);
+ void EnableJoyPollingReceiveMode(Kernel::HLERequestContext& ctx);
+ void DisableJoyPollingReceiveMode(Kernel::HLERequestContext& ctx);
+ void SetStatusManagerType(Kernel::HLERequestContext& ctx);
+
+ void UpdateHidbus(std::uintptr_t user_data, std::chrono::nanoseconds ns_late);
+ std::optional<std::size_t> GetDeviceIndexFromHandle(BusHandle handle) const;
+
+ template <typename T>
+ void MakeDevice(BusHandle handle) {
+ const auto device_index = GetDeviceIndexFromHandle(handle);
+ if (device_index) {
+ devices[device_index.value()].device =
+ std::make_unique<T>(system.HIDCore(), service_context);
+ }
+ }
+
+ bool is_hidbus_enabled{false};
+ HidbusStatusManager hidbus_status{};
+ std::array<HidbusDevice, max_number_of_handles> devices{};
+ std::shared_ptr<Core::Timing::EventType> hidbus_update_event;
+ KernelHelpers::ServiceContext service_context;
+};
+
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/hidbus/hidbus_base.cpp b/src/core/hle/service/hid/hidbus/hidbus_base.cpp
new file mode 100644
index 000000000..09bff10e5
--- /dev/null
+++ b/src/core/hle/service/hid/hidbus/hidbus_base.cpp
@@ -0,0 +1,72 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hid/hid_core.h"
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/kernel/k_readable_event.h"
+#include "core/hle/service/hid/hidbus/hidbus_base.h"
+#include "core/hle/service/kernel_helpers.h"
+
+namespace Service::HID {
+
+HidbusBase::HidbusBase(KernelHelpers::ServiceContext& service_context_)
+ : service_context(service_context_) {
+ send_command_async_event = service_context.CreateEvent("hidbus:SendCommandAsyncEvent");
+}
+HidbusBase::~HidbusBase() = default;
+
+void HidbusBase::ActivateDevice() {
+ if (is_activated) {
+ return;
+ }
+ is_activated = true;
+ OnInit();
+}
+
+void HidbusBase::DeactivateDevice() {
+ if (is_activated) {
+ OnRelease();
+ }
+ is_activated = false;
+}
+
+bool HidbusBase::IsDeviceActivated() const {
+ return is_activated;
+}
+
+void HidbusBase::Enable(bool enable) {
+ device_enabled = enable;
+}
+
+bool HidbusBase::IsEnabled() const {
+ return device_enabled;
+}
+
+bool HidbusBase::IsPollingMode() const {
+ return polling_mode_enabled;
+}
+
+JoyPollingMode HidbusBase::GetPollingMode() const {
+ return polling_mode;
+}
+
+void HidbusBase::SetPollingMode(JoyPollingMode mode) {
+ polling_mode = mode;
+ polling_mode_enabled = true;
+}
+
+void HidbusBase::DisablePollingMode() {
+ polling_mode_enabled = false;
+}
+
+void HidbusBase::SetTransferMemoryPointer(u8* t_mem) {
+ is_transfer_memory_set = true;
+ transfer_memory = t_mem;
+}
+
+Kernel::KReadableEvent& HidbusBase::GetSendCommandAsycEvent() const {
+ return send_command_async_event->GetReadableEvent();
+}
+
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/hidbus/hidbus_base.h b/src/core/hle/service/hid/hidbus/hidbus_base.h
new file mode 100644
index 000000000..13d073a3d
--- /dev/null
+++ b/src/core/hle/service/hid/hidbus/hidbus_base.h
@@ -0,0 +1,179 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include "common/common_types.h"
+#include "core/hle/result.h"
+
+namespace Kernel {
+class KEvent;
+class KReadableEvent;
+} // namespace Kernel
+
+namespace Service::KernelHelpers {
+class ServiceContext;
+}
+
+namespace Service::HID {
+
+// This is nn::hidbus::JoyPollingMode
+enum class JoyPollingMode : u32 {
+ SixAxisSensorDisable,
+ SixAxisSensorEnable,
+ ButtonOnly,
+};
+
+struct DataAccessorHeader {
+ ResultCode result{ResultUnknown};
+ INSERT_PADDING_WORDS(0x1);
+ std::array<u8, 0x18> unused{};
+ u64 latest_entry{};
+ u64 total_entries{};
+};
+static_assert(sizeof(DataAccessorHeader) == 0x30, "DataAccessorHeader is an invalid size");
+
+struct JoyDisableSixAxisPollingData {
+ std::array<u8, 0x26> data;
+ u8 out_size;
+ INSERT_PADDING_BYTES(0x1);
+ u64 sampling_number;
+};
+static_assert(sizeof(JoyDisableSixAxisPollingData) == 0x30,
+ "JoyDisableSixAxisPollingData is an invalid size");
+
+struct JoyEnableSixAxisPollingData {
+ std::array<u8, 0x8> data;
+ u8 out_size;
+ INSERT_PADDING_BYTES(0x7);
+ u64 sampling_number;
+};
+static_assert(sizeof(JoyEnableSixAxisPollingData) == 0x18,
+ "JoyEnableSixAxisPollingData is an invalid size");
+
+struct JoyButtonOnlyPollingData {
+ std::array<u8, 0x2c> data;
+ u8 out_size;
+ INSERT_PADDING_BYTES(0x3);
+ u64 sampling_number;
+};
+static_assert(sizeof(JoyButtonOnlyPollingData) == 0x38,
+ "JoyButtonOnlyPollingData is an invalid size");
+
+struct JoyDisableSixAxisPollingEntry {
+ u64 sampling_number;
+ JoyDisableSixAxisPollingData polling_data;
+};
+static_assert(sizeof(JoyDisableSixAxisPollingEntry) == 0x38,
+ "JoyDisableSixAxisPollingEntry is an invalid size");
+
+struct JoyEnableSixAxisPollingEntry {
+ u64 sampling_number;
+ JoyEnableSixAxisPollingData polling_data;
+};
+static_assert(sizeof(JoyEnableSixAxisPollingEntry) == 0x20,
+ "JoyEnableSixAxisPollingEntry is an invalid size");
+
+struct JoyButtonOnlyPollingEntry {
+ u64 sampling_number;
+ JoyButtonOnlyPollingData polling_data;
+};
+static_assert(sizeof(JoyButtonOnlyPollingEntry) == 0x40,
+ "JoyButtonOnlyPollingEntry is an invalid size");
+
+struct JoyDisableSixAxisDataAccessor {
+ DataAccessorHeader header{};
+ std::array<JoyDisableSixAxisPollingEntry, 0xb> entries{};
+};
+static_assert(sizeof(JoyDisableSixAxisDataAccessor) == 0x298,
+ "JoyDisableSixAxisDataAccessor is an invalid size");
+
+struct JoyEnableSixAxisDataAccessor {
+ DataAccessorHeader header{};
+ std::array<JoyEnableSixAxisPollingEntry, 0xb> entries{};
+};
+static_assert(sizeof(JoyEnableSixAxisDataAccessor) == 0x190,
+ "JoyEnableSixAxisDataAccessor is an invalid size");
+
+struct ButtonOnlyPollingDataAccessor {
+ DataAccessorHeader header;
+ std::array<JoyButtonOnlyPollingEntry, 0xb> entries;
+};
+static_assert(sizeof(ButtonOnlyPollingDataAccessor) == 0x2F0,
+ "ButtonOnlyPollingDataAccessor is an invalid size");
+
+class HidbusBase {
+public:
+ explicit HidbusBase(KernelHelpers::ServiceContext& service_context_);
+ virtual ~HidbusBase();
+
+ void ActivateDevice();
+
+ void DeactivateDevice();
+
+ bool IsDeviceActivated() const;
+
+ // Enables/disables the device
+ void Enable(bool enable);
+
+ // returns true if device is enabled
+ bool IsEnabled() const;
+
+ // returns true if polling mode is enabled
+ bool IsPollingMode() const;
+
+ // returns polling mode
+ JoyPollingMode GetPollingMode() const;
+
+ // Sets and enables JoyPollingMode
+ void SetPollingMode(JoyPollingMode mode);
+
+ // Disables JoyPollingMode
+ void DisablePollingMode();
+
+ // Called on EnableJoyPollingReceiveMode
+ void SetTransferMemoryPointer(u8* t_mem);
+
+ Kernel::KReadableEvent& GetSendCommandAsycEvent() const;
+
+ virtual void OnInit() {}
+
+ virtual void OnRelease() {}
+
+ // Updates device transfer memory
+ virtual void OnUpdate() {}
+
+ // Returns the device ID of the joycon
+ virtual u8 GetDeviceId() const {
+ return {};
+ }
+
+ // Assigns a command from data
+ virtual bool SetCommand(const std::vector<u8>& data) {
+ return {};
+ }
+
+ // Returns a reply from a command
+ virtual std::vector<u8> GetReply() const {
+ return {};
+ }
+
+protected:
+ bool is_activated{};
+ bool device_enabled{};
+ bool polling_mode_enabled{};
+ JoyPollingMode polling_mode = {};
+ // TODO(German77): All data accessors need to be replaced with a ring lifo object
+ JoyDisableSixAxisDataAccessor disable_sixaxis_data{};
+ JoyEnableSixAxisDataAccessor enable_sixaxis_data{};
+ ButtonOnlyPollingDataAccessor button_only_data{};
+
+ u8* transfer_memory{nullptr};
+ bool is_transfer_memory_set{};
+
+ Kernel::KEvent* send_command_async_event;
+ KernelHelpers::ServiceContext& service_context;
+};
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/hidbus/ringcon.cpp b/src/core/hle/service/hid/hidbus/ringcon.cpp
new file mode 100644
index 000000000..5ec3cc83c
--- /dev/null
+++ b/src/core/hle/service/hid/hidbus/ringcon.cpp
@@ -0,0 +1,286 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hid/emulated_devices.h"
+#include "core/hid/hid_core.h"
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/kernel/k_readable_event.h"
+#include "core/hle/service/hid/hidbus/ringcon.h"
+
+namespace Service::HID {
+
+RingController::RingController(Core::HID::HIDCore& hid_core_,
+ KernelHelpers::ServiceContext& service_context_)
+ : HidbusBase(service_context_) {
+ input = hid_core_.GetEmulatedDevices();
+}
+
+RingController::~RingController() = default;
+
+void RingController::OnInit() {
+ return;
+}
+
+void RingController::OnRelease() {
+ return;
+};
+
+void RingController::OnUpdate() {
+ if (!is_activated) {
+ return;
+ }
+
+ if (!device_enabled) {
+ return;
+ }
+
+ if (!polling_mode_enabled || !is_transfer_memory_set) {
+ return;
+ }
+
+ // TODO: Increment multitasking counters from motion and sensor data
+
+ switch (polling_mode) {
+ case JoyPollingMode::SixAxisSensorEnable: {
+ enable_sixaxis_data.header.total_entries = 10;
+ enable_sixaxis_data.header.result = ResultSuccess;
+ const auto& last_entry =
+ enable_sixaxis_data.entries[enable_sixaxis_data.header.latest_entry];
+
+ enable_sixaxis_data.header.latest_entry =
+ (enable_sixaxis_data.header.latest_entry + 1) % 10;
+ auto& curr_entry = enable_sixaxis_data.entries[enable_sixaxis_data.header.latest_entry];
+
+ curr_entry.sampling_number = last_entry.sampling_number + 1;
+ curr_entry.polling_data.sampling_number = curr_entry.sampling_number;
+
+ const RingConData ringcon_value = GetSensorValue();
+ curr_entry.polling_data.out_size = sizeof(ringcon_value);
+ std::memcpy(curr_entry.polling_data.data.data(), &ringcon_value, sizeof(ringcon_value));
+
+ std::memcpy(transfer_memory, &enable_sixaxis_data, sizeof(enable_sixaxis_data));
+ break;
+ }
+ default:
+ LOG_ERROR(Service_HID, "Polling mode not supported {}", polling_mode);
+ break;
+ }
+}
+
+RingController::RingConData RingController::GetSensorValue() const {
+ RingConData ringcon_sensor_value{
+ .status = DataValid::Valid,
+ .data = 0,
+ };
+
+ const f32 force_value = input->GetRingSensorForce().force * range;
+ ringcon_sensor_value.data = static_cast<s16>(force_value) + idle_value;
+
+ return ringcon_sensor_value;
+}
+
+u8 RingController::GetDeviceId() const {
+ return device_id;
+}
+
+std::vector<u8> RingController::GetReply() const {
+ const RingConCommands current_command = command;
+
+ switch (current_command) {
+ case RingConCommands::GetFirmwareVersion:
+ return GetFirmwareVersionReply();
+ case RingConCommands::ReadId:
+ return GetReadIdReply();
+ case RingConCommands::c20105:
+ return GetC020105Reply();
+ case RingConCommands::ReadUnkCal:
+ return GetReadUnkCalReply();
+ case RingConCommands::ReadFactoryCal:
+ return GetReadFactoryCalReply();
+ case RingConCommands::ReadUserCal:
+ return GetReadUserCalReply();
+ case RingConCommands::ReadRepCount:
+ return GetReadRepCountReply();
+ case RingConCommands::ReadTotalPushCount:
+ return GetReadTotalPushCountReply();
+ case RingConCommands::ResetRepCount:
+ return GetResetRepCountReply();
+ case RingConCommands::SaveCalData:
+ return GetSaveDataReply();
+ default:
+ return GetErrorReply();
+ }
+}
+
+bool RingController::SetCommand(const std::vector<u8>& data) {
+ if (data.size() < 4) {
+ LOG_ERROR(Service_HID, "Command size not supported {}", data.size());
+ command = RingConCommands::Error;
+ return false;
+ }
+
+ std::memcpy(&command, data.data(), sizeof(RingConCommands));
+
+ switch (command) {
+ case RingConCommands::GetFirmwareVersion:
+ case RingConCommands::ReadId:
+ case RingConCommands::c20105:
+ case RingConCommands::ReadUnkCal:
+ case RingConCommands::ReadFactoryCal:
+ case RingConCommands::ReadUserCal:
+ case RingConCommands::ReadRepCount:
+ case RingConCommands::ReadTotalPushCount:
+ ASSERT_MSG(data.size() == 0x4, "data.size is not 0x4 bytes");
+ send_command_async_event->GetWritableEvent().Signal();
+ return true;
+ case RingConCommands::ResetRepCount:
+ ASSERT_MSG(data.size() == 0x4, "data.size is not 0x4 bytes");
+ total_rep_count = 0;
+ send_command_async_event->GetWritableEvent().Signal();
+ return true;
+ case RingConCommands::SaveCalData: {
+ ASSERT_MSG(data.size() == 0x14, "data.size is not 0x14 bytes");
+
+ SaveCalData save_info{};
+ std::memcpy(&save_info, data.data(), sizeof(SaveCalData));
+ user_calibration = save_info.calibration;
+ send_command_async_event->GetWritableEvent().Signal();
+ return true;
+ }
+ default:
+ LOG_ERROR(Service_HID, "Command not implemented {}", command);
+ command = RingConCommands::Error;
+ // Signal a reply to avoid softlocking the game
+ send_command_async_event->GetWritableEvent().Signal();
+ return false;
+ }
+}
+
+std::vector<u8> RingController::GetFirmwareVersionReply() const {
+ const FirmwareVersionReply reply{
+ .status = DataValid::Valid,
+ .firmware = version,
+ };
+
+ return GetDataVector(reply);
+}
+
+std::vector<u8> RingController::GetReadIdReply() const {
+ // The values are hardcoded from a real joycon
+ const ReadIdReply reply{
+ .status = DataValid::Valid,
+ .id_l_x0 = 8,
+ .id_l_x0_2 = 41,
+ .id_l_x4 = 22294,
+ .id_h_x0 = 19777,
+ .id_h_x0_2 = 13621,
+ .id_h_x4 = 8245,
+ };
+
+ return GetDataVector(reply);
+}
+
+std::vector<u8> RingController::GetC020105Reply() const {
+ const Cmd020105Reply reply{
+ .status = DataValid::Valid,
+ .data = 1,
+ };
+
+ return GetDataVector(reply);
+}
+
+std::vector<u8> RingController::GetReadUnkCalReply() const {
+ const ReadUnkCalReply reply{
+ .status = DataValid::Valid,
+ .data = 0,
+ };
+
+ return GetDataVector(reply);
+}
+
+std::vector<u8> RingController::GetReadFactoryCalReply() const {
+ const ReadFactoryCalReply reply{
+ .status = DataValid::Valid,
+ .calibration = factory_calibration,
+ };
+
+ return GetDataVector(reply);
+}
+
+std::vector<u8> RingController::GetReadUserCalReply() const {
+ const ReadUserCalReply reply{
+ .status = DataValid::Valid,
+ .calibration = user_calibration,
+ };
+
+ return GetDataVector(reply);
+}
+
+std::vector<u8> RingController::GetReadRepCountReply() const {
+ const GetThreeByteReply reply{
+ .status = DataValid::Valid,
+ .data = {total_rep_count, 0, 0},
+ .crc = GetCrcValue({total_rep_count, 0, 0, 0}),
+ };
+
+ return GetDataVector(reply);
+}
+
+std::vector<u8> RingController::GetReadTotalPushCountReply() const {
+ const GetThreeByteReply reply{
+ .status = DataValid::Valid,
+ .data = {total_push_count, 0, 0},
+ .crc = GetCrcValue({total_push_count, 0, 0, 0}),
+ };
+
+ return GetDataVector(reply);
+}
+
+std::vector<u8> RingController::GetResetRepCountReply() const {
+ return GetReadRepCountReply();
+}
+
+std::vector<u8> RingController::GetSaveDataReply() const {
+ const StatusReply reply{
+ .status = DataValid::Valid,
+ };
+
+ return GetDataVector(reply);
+}
+
+std::vector<u8> RingController::GetErrorReply() const {
+ const ErrorReply reply{
+ .status = DataValid::BadCRC,
+ };
+
+ return GetDataVector(reply);
+}
+
+u8 RingController::GetCrcValue(const std::vector<u8>& data) const {
+ u8 crc = 0;
+ for (std::size_t index = 0; index < data.size(); index++) {
+ for (u8 i = 0x80; i > 0; i >>= 1) {
+ bool bit = (crc & 0x80) != 0;
+ if ((data[index] & i) != 0) {
+ bit = !bit;
+ }
+ crc <<= 1;
+ if (bit) {
+ crc ^= 0x8d;
+ }
+ }
+ }
+ return crc;
+}
+
+template <typename T>
+std::vector<u8> RingController::GetDataVector(const T& reply) const {
+ static_assert(std::is_trivially_copyable_v<T>);
+ std::vector<u8> data;
+ data.resize(sizeof(reply));
+ std::memcpy(data.data(), &reply, sizeof(reply));
+ return data;
+}
+
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/hidbus/ringcon.h b/src/core/hle/service/hid/hidbus/ringcon.h
new file mode 100644
index 000000000..2dbc6150e
--- /dev/null
+++ b/src/core/hle/service/hid/hidbus/ringcon.h
@@ -0,0 +1,254 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+
+#include "common/common_types.h"
+#include "core/hle/service/hid/hidbus/hidbus_base.h"
+
+namespace Core::HID {
+class EmulatedDevices;
+} // namespace Core::HID
+
+namespace Service::HID {
+
+class RingController final : public HidbusBase {
+public:
+ explicit RingController(Core::HID::HIDCore& hid_core_,
+ KernelHelpers::ServiceContext& service_context_);
+ ~RingController() override;
+
+ void OnInit() override;
+
+ void OnRelease() override;
+
+ // Updates ringcon transfer memory
+ void OnUpdate() override;
+
+ // Returns the device ID of the joycon
+ u8 GetDeviceId() const override;
+
+ // Assigns a command from data
+ bool SetCommand(const std::vector<u8>& data) override;
+
+ // Returns a reply from a command
+ std::vector<u8> GetReply() const override;
+
+private:
+ // These values are obtained from a real ring controller
+ static constexpr s16 idle_value = 2280;
+ static constexpr s16 idle_deadzone = 120;
+ static constexpr s16 range = 2500;
+
+ // Most missing command names are leftovers from other firmware versions
+ enum class RingConCommands : u32 {
+ GetFirmwareVersion = 0x00020000,
+ ReadId = 0x00020100,
+ JoyPolling = 0x00020101,
+ Unknown1 = 0x00020104,
+ c20105 = 0x00020105,
+ Unknown2 = 0x00020204,
+ Unknown3 = 0x00020304,
+ Unknown4 = 0x00020404,
+ ReadUnkCal = 0x00020504,
+ ReadFactoryCal = 0x00020A04,
+ Unknown5 = 0x00021104,
+ Unknown6 = 0x00021204,
+ Unknown7 = 0x00021304,
+ ReadUserCal = 0x00021A04,
+ ReadRepCount = 0x00023104,
+ ReadTotalPushCount = 0x00023204,
+ ResetRepCount = 0x04013104,
+ Unknown8 = 0x04011104,
+ Unknown9 = 0x04011204,
+ Unknown10 = 0x04011304,
+ SaveCalData = 0x10011A04,
+ Error = 0xFFFFFFFF,
+ };
+
+ enum class DataValid : u32 {
+ Valid,
+ BadCRC,
+ Cal,
+ };
+
+ struct FirmwareVersion {
+ u8 sub;
+ u8 main;
+ };
+ static_assert(sizeof(FirmwareVersion) == 0x2, "FirmwareVersion is an invalid size");
+
+ struct FactoryCalibration {
+ s32_le os_max;
+ s32_le hk_max;
+ s32_le zero_min;
+ s32_le zero_max;
+ };
+ static_assert(sizeof(FactoryCalibration) == 0x10, "FactoryCalibration is an invalid size");
+
+ struct CalibrationValue {
+ s16 value;
+ u16 crc;
+ };
+ static_assert(sizeof(CalibrationValue) == 0x4, "CalibrationValue is an invalid size");
+
+ struct UserCalibration {
+ CalibrationValue os_max;
+ CalibrationValue hk_max;
+ CalibrationValue zero;
+ };
+ static_assert(sizeof(UserCalibration) == 0xC, "UserCalibration is an invalid size");
+
+ struct SaveCalData {
+ RingConCommands command;
+ UserCalibration calibration;
+ INSERT_PADDING_BYTES_NOINIT(4);
+ };
+ static_assert(sizeof(SaveCalData) == 0x14, "SaveCalData is an invalid size");
+ static_assert(std::is_trivially_copyable_v<SaveCalData>,
+ "SaveCalData must be trivially copyable");
+
+ struct FirmwareVersionReply {
+ DataValid status;
+ FirmwareVersion firmware;
+ INSERT_PADDING_BYTES(0x2);
+ };
+ static_assert(sizeof(FirmwareVersionReply) == 0x8, "FirmwareVersionReply is an invalid size");
+
+ struct Cmd020105Reply {
+ DataValid status;
+ u8 data;
+ INSERT_PADDING_BYTES(0x3);
+ };
+ static_assert(sizeof(Cmd020105Reply) == 0x8, "Cmd020105Reply is an invalid size");
+
+ struct StatusReply {
+ DataValid status;
+ };
+ static_assert(sizeof(StatusReply) == 0x4, "StatusReply is an invalid size");
+
+ struct GetThreeByteReply {
+ DataValid status;
+ std::array<u8, 3> data;
+ u8 crc;
+ };
+ static_assert(sizeof(GetThreeByteReply) == 0x8, "GetThreeByteReply is an invalid size");
+
+ struct ReadUnkCalReply {
+ DataValid status;
+ u16 data;
+ INSERT_PADDING_BYTES(0x2);
+ };
+ static_assert(sizeof(ReadUnkCalReply) == 0x8, "ReadUnkCalReply is an invalid size");
+
+ struct ReadFactoryCalReply {
+ DataValid status;
+ FactoryCalibration calibration;
+ };
+ static_assert(sizeof(ReadFactoryCalReply) == 0x14, "ReadFactoryCalReply is an invalid size");
+
+ struct ReadUserCalReply {
+ DataValid status;
+ UserCalibration calibration;
+ INSERT_PADDING_BYTES(0x4);
+ };
+ static_assert(sizeof(ReadUserCalReply) == 0x14, "ReadUserCalReply is an invalid size");
+
+ struct ReadIdReply {
+ DataValid status;
+ u16 id_l_x0;
+ u16 id_l_x0_2;
+ u16 id_l_x4;
+ u16 id_h_x0;
+ u16 id_h_x0_2;
+ u16 id_h_x4;
+ };
+ static_assert(sizeof(ReadIdReply) == 0x10, "ReadIdReply is an invalid size");
+
+ struct ErrorReply {
+ DataValid status;
+ INSERT_PADDING_BYTES(0x3);
+ };
+ static_assert(sizeof(ErrorReply) == 0x8, "ErrorReply is an invalid size");
+
+ struct RingConData {
+ DataValid status;
+ s16_le data;
+ INSERT_PADDING_BYTES(0x2);
+ };
+ static_assert(sizeof(RingConData) == 0x8, "RingConData is an invalid size");
+
+ // Returns RingConData struct with pressure sensor values
+ RingConData GetSensorValue() const;
+
+ // Returns 8 byte reply with firmware version
+ std::vector<u8> GetFirmwareVersionReply() const;
+
+ // Returns 16 byte reply with ID values
+ std::vector<u8> GetReadIdReply() const;
+
+ // (STUBBED) Returns 8 byte reply
+ std::vector<u8> GetC020105Reply() const;
+
+ // (STUBBED) Returns 8 byte empty reply
+ std::vector<u8> GetReadUnkCalReply() const;
+
+ // Returns 20 byte reply with factory calibration values
+ std::vector<u8> GetReadFactoryCalReply() const;
+
+ // Returns 20 byte reply with user calibration values
+ std::vector<u8> GetReadUserCalReply() const;
+
+ // Returns 8 byte reply
+ std::vector<u8> GetReadRepCountReply() const;
+
+ // Returns 8 byte reply
+ std::vector<u8> GetReadTotalPushCountReply() const;
+
+ // Returns 8 byte reply
+ std::vector<u8> GetResetRepCountReply() const;
+
+ // Returns 4 byte save data reply
+ std::vector<u8> GetSaveDataReply() const;
+
+ // Returns 8 byte error reply
+ std::vector<u8> GetErrorReply() const;
+
+ // Returns 8 bit redundancy check from provided data
+ u8 GetCrcValue(const std::vector<u8>& data) const;
+
+ // Converts structs to an u8 vector equivalent
+ template <typename T>
+ std::vector<u8> GetDataVector(const T& reply) const;
+
+ RingConCommands command{RingConCommands::Error};
+
+ // These counters are used in multitasking mode while the switch is sleeping
+ // Total steps taken
+ u8 total_rep_count = 0;
+ // Total times the ring was pushed
+ u8 total_push_count = 0;
+
+ const u8 device_id = 0x20;
+ const FirmwareVersion version = {
+ .sub = 0x0,
+ .main = 0x2c,
+ };
+ const FactoryCalibration factory_calibration = {
+ .os_max = idle_value + range + idle_deadzone,
+ .hk_max = idle_value - range - idle_deadzone,
+ .zero_min = idle_value - idle_deadzone,
+ .zero_max = idle_value + idle_deadzone,
+ };
+ UserCalibration user_calibration = {
+ .os_max = {.value = range, .crc = 228},
+ .hk_max = {.value = -range, .crc = 239},
+ .zero = {.value = idle_value, .crc = 225},
+ };
+
+ Core::HID::EmulatedDevices* input;
+};
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/hidbus/starlink.cpp b/src/core/hle/service/hid/hidbus/starlink.cpp
new file mode 100644
index 000000000..3175c48da
--- /dev/null
+++ b/src/core/hle/service/hid/hidbus/starlink.cpp
@@ -0,0 +1,51 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hid/emulated_controller.h"
+#include "core/hid/hid_core.h"
+#include "core/hle/service/hid/hidbus/starlink.h"
+
+namespace Service::HID {
+constexpr u8 DEVICE_ID = 0x28;
+
+Starlink::Starlink(Core::HID::HIDCore& hid_core_, KernelHelpers::ServiceContext& service_context_)
+ : HidbusBase(service_context_) {}
+Starlink::~Starlink() = default;
+
+void Starlink::OnInit() {
+ return;
+}
+
+void Starlink::OnRelease() {
+ return;
+};
+
+void Starlink::OnUpdate() {
+ if (!is_activated) {
+ return;
+ }
+ if (!device_enabled) {
+ return;
+ }
+ if (!polling_mode_enabled || !is_transfer_memory_set) {
+ return;
+ }
+
+ LOG_ERROR(Service_HID, "Polling mode not supported {}", polling_mode);
+}
+
+u8 Starlink::GetDeviceId() const {
+ return DEVICE_ID;
+}
+
+std::vector<u8> Starlink::GetReply() const {
+ return {};
+}
+
+bool Starlink::SetCommand(const std::vector<u8>& data) {
+ LOG_ERROR(Service_HID, "Command not implemented");
+ return false;
+}
+
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/hidbus/starlink.h b/src/core/hle/service/hid/hidbus/starlink.h
new file mode 100644
index 000000000..79770b68e
--- /dev/null
+++ b/src/core/hle/service/hid/hidbus/starlink.h
@@ -0,0 +1,39 @@
+// Copyright 2021 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 "core/hle/service/hid/hidbus/hidbus_base.h"
+
+namespace Core::HID {
+class EmulatedController;
+} // namespace Core::HID
+
+namespace Service::HID {
+
+class Starlink final : public HidbusBase {
+public:
+ explicit Starlink(Core::HID::HIDCore& hid_core_,
+ KernelHelpers::ServiceContext& service_context_);
+ ~Starlink() override;
+
+ void OnInit() override;
+
+ void OnRelease() override;
+
+ // Updates ringcon transfer memory
+ void OnUpdate() override;
+
+ // Returns the device ID of the joycon
+ u8 GetDeviceId() const override;
+
+ // Assigns a command from data
+ bool SetCommand(const std::vector<u8>& data) override;
+
+ // Returns a reply from a command
+ std::vector<u8> GetReply() const override;
+};
+
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/hidbus/stubbed.cpp b/src/core/hle/service/hid/hidbus/stubbed.cpp
new file mode 100644
index 000000000..5474657be
--- /dev/null
+++ b/src/core/hle/service/hid/hidbus/stubbed.cpp
@@ -0,0 +1,52 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hid/emulated_controller.h"
+#include "core/hid/hid_core.h"
+#include "core/hle/service/hid/hidbus/stubbed.h"
+
+namespace Service::HID {
+constexpr u8 DEVICE_ID = 0xFF;
+
+HidbusStubbed::HidbusStubbed(Core::HID::HIDCore& hid_core_,
+ KernelHelpers::ServiceContext& service_context_)
+ : HidbusBase(service_context_) {}
+HidbusStubbed::~HidbusStubbed() = default;
+
+void HidbusStubbed::OnInit() {
+ return;
+}
+
+void HidbusStubbed::OnRelease() {
+ return;
+};
+
+void HidbusStubbed::OnUpdate() {
+ if (!is_activated) {
+ return;
+ }
+ if (!device_enabled) {
+ return;
+ }
+ if (!polling_mode_enabled || !is_transfer_memory_set) {
+ return;
+ }
+
+ LOG_ERROR(Service_HID, "Polling mode not supported {}", polling_mode);
+}
+
+u8 HidbusStubbed::GetDeviceId() const {
+ return DEVICE_ID;
+}
+
+std::vector<u8> HidbusStubbed::GetReply() const {
+ return {};
+}
+
+bool HidbusStubbed::SetCommand(const std::vector<u8>& data) {
+ LOG_ERROR(Service_HID, "Command not implemented");
+ return false;
+}
+
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/hidbus/stubbed.h b/src/core/hle/service/hid/hidbus/stubbed.h
new file mode 100644
index 000000000..40acdfe8f
--- /dev/null
+++ b/src/core/hle/service/hid/hidbus/stubbed.h
@@ -0,0 +1,39 @@
+// Copyright 2021 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 "core/hle/service/hid/hidbus/hidbus_base.h"
+
+namespace Core::HID {
+class EmulatedController;
+} // namespace Core::HID
+
+namespace Service::HID {
+
+class HidbusStubbed final : public HidbusBase {
+public:
+ explicit HidbusStubbed(Core::HID::HIDCore& hid_core_,
+ KernelHelpers::ServiceContext& service_context_);
+ ~HidbusStubbed() override;
+
+ void OnInit() override;
+
+ void OnRelease() override;
+
+ // Updates ringcon transfer memory
+ void OnUpdate() override;
+
+ // Returns the device ID of the joycon
+ u8 GetDeviceId() const override;
+
+ // Assigns a command from data
+ bool SetCommand(const std::vector<u8>& data) override;
+
+ // Returns a reply from a command
+ std::vector<u8> GetReply() const override;
+};
+
+} // namespace Service::HID
diff --git a/src/core/hle/service/jit/jit.cpp b/src/core/hle/service/jit/jit.cpp
new file mode 100644
index 000000000..0f9e33ef6
--- /dev/null
+++ b/src/core/hle/service/jit/jit.cpp
@@ -0,0 +1,332 @@
+// Copyright 2022 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/arm/symbols.h"
+#include "core/core.h"
+#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/k_code_memory.h"
+#include "core/hle/kernel/k_transfer_memory.h"
+#include "core/hle/result.h"
+#include "core/hle/service/jit/jit.h"
+#include "core/hle/service/jit/jit_context.h"
+#include "core/hle/service/service.h"
+#include "core/memory.h"
+
+namespace Service::JIT {
+
+struct CodeRange {
+ u64 offset;
+ u64 size;
+};
+
+class IJitEnvironment final : public ServiceFramework<IJitEnvironment> {
+public:
+ explicit IJitEnvironment(Core::System& system_, CodeRange user_rx, CodeRange user_ro)
+ : ServiceFramework{system_, "IJitEnvironment", ServiceThreadType::CreateNew},
+ context{system_.Memory()} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, &IJitEnvironment::GenerateCode, "GenerateCode"},
+ {1, &IJitEnvironment::Control, "Control"},
+ {1000, &IJitEnvironment::LoadPlugin, "LoadPlugin"},
+ {1001, &IJitEnvironment::GetCodeAddress, "GetCodeAddress"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+
+ // Identity map user code range into sysmodule context
+ configuration.user_ro_memory = user_ro;
+ configuration.user_rx_memory = user_rx;
+ configuration.sys_ro_memory = user_ro;
+ configuration.sys_rx_memory = user_rx;
+ }
+
+ void GenerateCode(Kernel::HLERequestContext& ctx) {
+ struct Parameters {
+ u32 data_size;
+ u64 command;
+ CodeRange cr1;
+ CodeRange cr2;
+ Struct32 data;
+ };
+
+ IPC::RequestParser rp{ctx};
+ const auto parameters{rp.PopRaw<Parameters>()};
+ std::vector<u8> input_buffer{ctx.CanReadBuffer() ? ctx.ReadBuffer() : std::vector<u8>()};
+ std::vector<u8> output_buffer(ctx.CanWriteBuffer() ? ctx.GetWriteBufferSize() : 0);
+
+ const VAddr return_ptr{context.AddHeap(0u)};
+ const VAddr cr1_in_ptr{context.AddHeap(parameters.cr1)};
+ const VAddr cr2_in_ptr{context.AddHeap(parameters.cr2)};
+ const VAddr cr1_out_ptr{
+ context.AddHeap(CodeRange{.offset = parameters.cr1.offset, .size = 0})};
+ const VAddr cr2_out_ptr{
+ context.AddHeap(CodeRange{.offset = parameters.cr2.offset, .size = 0})};
+ const VAddr input_ptr{context.AddHeap(input_buffer.data(), input_buffer.size())};
+ const VAddr output_ptr{context.AddHeap(output_buffer.data(), output_buffer.size())};
+ const VAddr data_ptr{context.AddHeap(parameters.data)};
+ const VAddr configuration_ptr{context.AddHeap(configuration)};
+
+ context.CallFunction(callbacks.GenerateCode, return_ptr, cr1_out_ptr, cr2_out_ptr,
+ configuration_ptr, parameters.command, input_ptr, input_buffer.size(),
+ cr1_in_ptr, cr2_in_ptr, data_ptr, parameters.data_size, output_ptr,
+ output_buffer.size());
+
+ const s32 return_value{context.GetHeap<s32>(return_ptr)};
+
+ if (return_value == 0) {
+ system.InvalidateCpuInstructionCacheRange(configuration.user_rx_memory.offset,
+ configuration.user_rx_memory.size);
+
+ if (ctx.CanWriteBuffer()) {
+ context.GetHeap(output_ptr, output_buffer.data(), output_buffer.size());
+ ctx.WriteBuffer(output_buffer.data(), output_buffer.size());
+ }
+ const auto cr1_out{context.GetHeap<CodeRange>(cr1_out_ptr)};
+ const auto cr2_out{context.GetHeap<CodeRange>(cr2_out_ptr)};
+
+ IPC::ResponseBuilder rb{ctx, 8};
+ rb.Push(ResultSuccess);
+ rb.Push<u64>(return_value);
+ rb.PushRaw(cr1_out);
+ rb.PushRaw(cr2_out);
+ } else {
+ LOG_WARNING(Service_JIT, "plugin GenerateCode callback failed");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultUnknown);
+ }
+ };
+
+ void Control(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto command{rp.PopRaw<u64>()};
+ const auto input_buffer{ctx.ReadBuffer()};
+ std::vector<u8> output_buffer(ctx.CanWriteBuffer() ? ctx.GetWriteBufferSize() : 0);
+
+ const VAddr return_ptr{context.AddHeap(0u)};
+ const VAddr configuration_ptr{context.AddHeap(configuration)};
+ const VAddr input_ptr{context.AddHeap(input_buffer.data(), input_buffer.size())};
+ const VAddr output_ptr{context.AddHeap(output_buffer.data(), output_buffer.size())};
+ const u64 wrapper_value{
+ context.CallFunction(callbacks.Control, return_ptr, configuration_ptr, command,
+ input_ptr, input_buffer.size(), output_ptr, output_buffer.size())};
+ const s32 return_value{context.GetHeap<s32>(return_ptr)};
+
+ if (wrapper_value == 0 && return_value == 0) {
+ if (ctx.CanWriteBuffer()) {
+ context.GetHeap(output_ptr, output_buffer.data(), output_buffer.size());
+ ctx.WriteBuffer(output_buffer.data(), output_buffer.size());
+ }
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(return_value);
+ } else {
+ LOG_WARNING(Service_JIT, "plugin Control callback failed");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultUnknown);
+ }
+ }
+
+ void LoadPlugin(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto tmem_size{rp.PopRaw<u64>()};
+ if (tmem_size == 0) {
+ LOG_ERROR(Service_JIT, "attempted to load plugin with empty transfer memory");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultUnknown);
+ return;
+ }
+
+ const auto tmem_handle{ctx.GetCopyHandle(0)};
+ auto tmem{system.CurrentProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(
+ tmem_handle)};
+ if (tmem.IsNull()) {
+ LOG_ERROR(Service_JIT, "attempted to load plugin with invalid transfer memory handle");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultUnknown);
+ return;
+ }
+
+ configuration.work_memory.offset = tmem->GetSourceAddress();
+ configuration.work_memory.size = tmem_size;
+
+ const auto nro_plugin{ctx.ReadBuffer(1)};
+ auto symbols{Core::Symbols::GetSymbols(nro_plugin, true)};
+ const auto GetSymbol{[&](std::string name) { return symbols[name].first; }};
+
+ callbacks =
+ GuestCallbacks{.rtld_fini = GetSymbol("_fini"),
+ .rtld_init = GetSymbol("_init"),
+ .Control = GetSymbol("nnjitpluginControl"),
+ .ResolveBasicSymbols = GetSymbol("nnjitpluginResolveBasicSymbols"),
+ .SetupDiagnostics = GetSymbol("nnjitpluginSetupDiagnostics"),
+ .Configure = GetSymbol("nnjitpluginConfigure"),
+ .GenerateCode = GetSymbol("nnjitpluginGenerateCode"),
+ .GetVersion = GetSymbol("nnjitpluginGetVersion"),
+ .Keeper = GetSymbol("nnjitpluginKeeper"),
+ .OnPrepared = GetSymbol("nnjitpluginOnPrepared")};
+
+ if (callbacks.GetVersion == 0 || callbacks.Configure == 0 || callbacks.GenerateCode == 0 ||
+ callbacks.OnPrepared == 0) {
+ LOG_ERROR(Service_JIT, "plugin does not implement all necessary functionality");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultUnknown);
+ return;
+ }
+
+ if (!context.LoadNRO(nro_plugin)) {
+ LOG_ERROR(Service_JIT, "failed to load plugin");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultUnknown);
+ return;
+ }
+
+ context.MapProcessMemory(configuration.sys_ro_memory.offset,
+ configuration.sys_ro_memory.size);
+ context.MapProcessMemory(configuration.sys_rx_memory.offset,
+ configuration.sys_rx_memory.size);
+ context.MapProcessMemory(configuration.work_memory.offset, configuration.work_memory.size);
+
+ if (callbacks.rtld_init != 0) {
+ context.CallFunction(callbacks.rtld_init);
+ }
+
+ const auto version{context.CallFunction(callbacks.GetVersion)};
+ if (version != 1) {
+ LOG_ERROR(Service_JIT, "unknown plugin version {}", version);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultUnknown);
+ return;
+ }
+
+ const auto resolve{context.GetHelper("_resolve")};
+ if (callbacks.ResolveBasicSymbols != 0) {
+ context.CallFunction(callbacks.ResolveBasicSymbols, resolve);
+ }
+ const auto resolve_ptr{context.AddHeap(resolve)};
+ if (callbacks.SetupDiagnostics != 0) {
+ context.CallFunction(callbacks.SetupDiagnostics, 0u, resolve_ptr);
+ }
+
+ context.CallFunction(callbacks.Configure, 0u);
+ const auto configuration_ptr{context.AddHeap(configuration)};
+ context.CallFunction(callbacks.OnPrepared, configuration_ptr);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+ }
+
+ void GetCodeAddress(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 6};
+ rb.Push(ResultSuccess);
+ rb.Push(configuration.user_rx_memory.offset);
+ rb.Push(configuration.user_ro_memory.offset);
+ }
+
+private:
+ using Struct32 = std::array<u8, 32>;
+
+ struct GuestCallbacks {
+ VAddr rtld_fini;
+ VAddr rtld_init;
+ VAddr Control;
+ VAddr ResolveBasicSymbols;
+ VAddr SetupDiagnostics;
+ VAddr Configure;
+ VAddr GenerateCode;
+ VAddr GetVersion;
+ VAddr Keeper;
+ VAddr OnPrepared;
+ };
+
+ struct JITConfiguration {
+ CodeRange user_rx_memory;
+ CodeRange user_ro_memory;
+ CodeRange work_memory;
+ CodeRange sys_rx_memory;
+ CodeRange sys_ro_memory;
+ };
+
+ GuestCallbacks callbacks;
+ JITConfiguration configuration;
+ JITContext context;
+};
+
+class JITU final : public ServiceFramework<JITU> {
+public:
+ explicit JITU(Core::System& system_) : ServiceFramework{system_, "jit:u"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, &JITU::CreateJitEnvironment, "CreateJitEnvironment"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+
+ void CreateJitEnvironment(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_JIT, "called");
+
+ struct Parameters {
+ u64 rx_size;
+ u64 ro_size;
+ };
+
+ IPC::RequestParser rp{ctx};
+ const auto parameters{rp.PopRaw<Parameters>()};
+ const auto executable_mem_handle{ctx.GetCopyHandle(1)};
+ const auto readable_mem_handle{ctx.GetCopyHandle(2)};
+
+ if (parameters.rx_size == 0 || parameters.ro_size == 0) {
+ LOG_ERROR(Service_JIT, "attempted to init with empty code regions");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultUnknown);
+ return;
+ }
+
+ // The copy handle at index 0 is the process handle, but handle tables are
+ // per-process, so there is no point reading it here until we are multiprocess
+ const auto& process{*system.CurrentProcess()};
+
+ auto executable_mem{
+ process.GetHandleTable().GetObject<Kernel::KCodeMemory>(executable_mem_handle)};
+ if (executable_mem.IsNull()) {
+ LOG_ERROR(Service_JIT, "executable_mem is null for handle=0x{:08X}",
+ executable_mem_handle);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultUnknown);
+ return;
+ }
+
+ auto readable_mem{
+ process.GetHandleTable().GetObject<Kernel::KCodeMemory>(readable_mem_handle)};
+ if (readable_mem.IsNull()) {
+ LOG_ERROR(Service_JIT, "readable_mem is null for handle=0x{:08X}", readable_mem_handle);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultUnknown);
+ return;
+ }
+
+ const CodeRange user_rx{
+ .offset = executable_mem->GetSourceAddress(),
+ .size = parameters.rx_size,
+ };
+
+ const CodeRange user_ro{
+ .offset = readable_mem->GetSourceAddress(),
+ .size = parameters.ro_size,
+ };
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(ResultSuccess);
+ rb.PushIpcInterface<IJitEnvironment>(system, user_rx, user_ro);
+ }
+};
+
+void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) {
+ std::make_shared<JITU>(system)->InstallAsService(sm);
+}
+
+} // namespace Service::JIT
diff --git a/src/core/hle/service/jit/jit.h b/src/core/hle/service/jit/jit.h
new file mode 100644
index 000000000..8fbf504a1
--- /dev/null
+++ b/src/core/hle/service/jit/jit.h
@@ -0,0 +1,20 @@
+// Copyright 2022 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+namespace Core {
+class System;
+}
+
+namespace Service::SM {
+class ServiceManager;
+}
+
+namespace Service::JIT {
+
+/// Registers all JIT services with the specified service manager.
+void InstallInterfaces(SM::ServiceManager& sm, Core::System& system);
+
+} // namespace Service::JIT
diff --git a/src/core/hle/service/jit/jit_context.cpp b/src/core/hle/service/jit/jit_context.cpp
new file mode 100644
index 000000000..630368fb3
--- /dev/null
+++ b/src/core/hle/service/jit/jit_context.cpp
@@ -0,0 +1,424 @@
+// Copyright 2022 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <array>
+#include <map>
+#include <span>
+#include <boost/icl/interval_set.hpp>
+#include <dynarmic/interface/A64/a64.h>
+#include <dynarmic/interface/A64/config.h>
+
+#include "common/alignment.h"
+#include "common/common_funcs.h"
+#include "common/div_ceil.h"
+#include "common/logging/log.h"
+#include "core/hle/service/jit/jit_context.h"
+#include "core/memory.h"
+
+namespace Service::JIT {
+
+constexpr std::array<u8, 4> STOP_ARM64 = {
+ 0x01, 0x00, 0x00, 0xd4, // svc #0
+};
+
+constexpr std::array<u8, 8> RESOLVE_ARM64 = {
+ 0x21, 0x00, 0x00, 0xd4, // svc #1
+ 0xc0, 0x03, 0x5f, 0xd6, // ret
+};
+
+constexpr std::array<u8, 4> PANIC_ARM64 = {
+ 0x41, 0x00, 0x00, 0xd4, // svc #2
+};
+
+constexpr std::array<u8, 60> MEMMOVE_ARM64 = {
+ 0x1f, 0x00, 0x01, 0xeb, // cmp x0, x1
+ 0x83, 0x01, 0x00, 0x54, // b.lo #+34
+ 0x42, 0x04, 0x00, 0xd1, // sub x2, x2, 1
+ 0x22, 0x01, 0xf8, 0xb7, // tbnz x2, #63, #+36
+ 0x23, 0x68, 0x62, 0x38, // ldrb w3, [x1, x2]
+ 0x03, 0x68, 0x22, 0x38, // strb w3, [x0, x2]
+ 0xfc, 0xff, 0xff, 0x17, // b #-16
+ 0x24, 0x68, 0x63, 0x38, // ldrb w4, [x1, x3]
+ 0x04, 0x68, 0x23, 0x38, // strb w4, [x0, x3]
+ 0x63, 0x04, 0x00, 0x91, // add x3, x3, 1
+ 0x7f, 0x00, 0x02, 0xeb, // cmp x3, x2
+ 0x8b, 0xff, 0xff, 0x54, // b.lt #-16
+ 0xc0, 0x03, 0x5f, 0xd6, // ret
+ 0x03, 0x00, 0x80, 0xd2, // mov x3, 0
+ 0xfc, 0xff, 0xff, 0x17, // b #-16
+};
+
+constexpr std::array<u8, 28> MEMSET_ARM64 = {
+ 0x03, 0x00, 0x80, 0xd2, // mov x3, 0
+ 0x7f, 0x00, 0x02, 0xeb, // cmp x3, x2
+ 0x4b, 0x00, 0x00, 0x54, // b.lt #+8
+ 0xc0, 0x03, 0x5f, 0xd6, // ret
+ 0x01, 0x68, 0x23, 0x38, // strb w1, [x0, x3]
+ 0x63, 0x04, 0x00, 0x91, // add x3, x3, 1
+ 0xfb, 0xff, 0xff, 0x17, // b #-20
+};
+
+struct HelperFunction {
+ const char* name;
+ const std::span<const u8> data;
+};
+
+constexpr std::array<HelperFunction, 6> HELPER_FUNCTIONS{{
+ {"_stop", STOP_ARM64},
+ {"_resolve", RESOLVE_ARM64},
+ {"_panic", PANIC_ARM64},
+ {"memcpy", MEMMOVE_ARM64},
+ {"memmove", MEMMOVE_ARM64},
+ {"memset", MEMSET_ARM64},
+}};
+
+struct Elf64_Dyn {
+ u64 d_tag;
+ u64 d_un;
+};
+
+struct Elf64_Rela {
+ u64 r_offset;
+ u64 r_info;
+ s64 r_addend;
+};
+
+static constexpr u32 Elf64_RelaType(const Elf64_Rela* rela) {
+ return static_cast<u32>(rela->r_info);
+}
+
+constexpr int DT_RELA = 7; /* Address of Rela relocs */
+constexpr int DT_RELASZ = 8; /* Total size of Rela relocs */
+constexpr int R_AARCH64_RELATIVE = 1027; /* Adjust by program base. */
+
+constexpr size_t STACK_ALIGN = 16;
+
+class JITContextImpl;
+
+using IntervalSet = boost::icl::interval_set<VAddr>::type;
+using IntervalType = boost::icl::interval_set<VAddr>::interval_type;
+
+class DynarmicCallbacks64 : public Dynarmic::A64::UserCallbacks {
+public:
+ explicit DynarmicCallbacks64(Core::Memory::Memory& memory_, std::vector<u8>& local_memory_,
+ IntervalSet& mapped_ranges_, JITContextImpl& parent_)
+ : memory{memory_}, local_memory{local_memory_},
+ mapped_ranges{mapped_ranges_}, parent{parent_} {}
+
+ u8 MemoryRead8(u64 vaddr) override {
+ return ReadMemory<u8>(vaddr);
+ }
+ u16 MemoryRead16(u64 vaddr) override {
+ return ReadMemory<u16>(vaddr);
+ }
+ u32 MemoryRead32(u64 vaddr) override {
+ return ReadMemory<u32>(vaddr);
+ }
+ u64 MemoryRead64(u64 vaddr) override {
+ return ReadMemory<u64>(vaddr);
+ }
+ u128 MemoryRead128(u64 vaddr) override {
+ return ReadMemory<u128>(vaddr);
+ }
+ std::string MemoryReadCString(u64 vaddr) {
+ std::string result;
+ u8 next;
+
+ while ((next = MemoryRead8(vaddr++)) != 0) {
+ result += next;
+ }
+
+ return result;
+ }
+
+ void MemoryWrite8(u64 vaddr, u8 value) override {
+ WriteMemory<u8>(vaddr, value);
+ }
+ void MemoryWrite16(u64 vaddr, u16 value) override {
+ WriteMemory<u16>(vaddr, value);
+ }
+ void MemoryWrite32(u64 vaddr, u32 value) override {
+ WriteMemory<u32>(vaddr, value);
+ }
+ void MemoryWrite64(u64 vaddr, u64 value) override {
+ WriteMemory<u64>(vaddr, value);
+ }
+ void MemoryWrite128(u64 vaddr, u128 value) override {
+ WriteMemory<u128>(vaddr, value);
+ }
+
+ bool MemoryWriteExclusive8(u64 vaddr, u8 value, u8) override {
+ return WriteMemory<u8>(vaddr, value);
+ }
+ bool MemoryWriteExclusive16(u64 vaddr, u16 value, u16) override {
+ return WriteMemory<u16>(vaddr, value);
+ }
+ bool MemoryWriteExclusive32(u64 vaddr, u32 value, u32) override {
+ return WriteMemory<u32>(vaddr, value);
+ }
+ bool MemoryWriteExclusive64(u64 vaddr, u64 value, u64) override {
+ return WriteMemory<u64>(vaddr, value);
+ }
+ bool MemoryWriteExclusive128(u64 vaddr, u128 value, u128) override {
+ return WriteMemory<u128>(vaddr, value);
+ }
+
+ void CallSVC(u32 swi) override;
+ void ExceptionRaised(u64 pc, Dynarmic::A64::Exception exception) override;
+ void InterpreterFallback(u64 pc, size_t num_instructions) override;
+
+ void AddTicks(u64 ticks) override {}
+ u64 GetTicksRemaining() override {
+ return std::numeric_limits<u32>::max();
+ }
+ u64 GetCNTPCT() override {
+ return 0;
+ }
+
+ template <class T>
+ T ReadMemory(u64 vaddr) {
+ T ret{};
+ if (boost::icl::contains(mapped_ranges, vaddr)) {
+ memory.ReadBlock(vaddr, &ret, sizeof(T));
+ } else if (vaddr + sizeof(T) > local_memory.size()) {
+ LOG_CRITICAL(Service_JIT, "plugin: unmapped read @ 0x{:016x}", vaddr);
+ } else {
+ std::memcpy(&ret, local_memory.data() + vaddr, sizeof(T));
+ }
+ return ret;
+ }
+
+ template <class T>
+ bool WriteMemory(u64 vaddr, const T value) {
+ if (boost::icl::contains(mapped_ranges, vaddr)) {
+ memory.WriteBlock(vaddr, &value, sizeof(T));
+ } else if (vaddr + sizeof(T) > local_memory.size()) {
+ LOG_CRITICAL(Service_JIT, "plugin: unmapped write @ 0x{:016x}", vaddr);
+ } else {
+ std::memcpy(local_memory.data() + vaddr, &value, sizeof(T));
+ }
+ return true;
+ }
+
+private:
+ Core::Memory::Memory& memory;
+ std::vector<u8>& local_memory;
+ IntervalSet& mapped_ranges;
+ JITContextImpl& parent;
+};
+
+class JITContextImpl {
+public:
+ explicit JITContextImpl(Core::Memory::Memory& memory_) : memory{memory_} {
+ callbacks =
+ std::make_unique<DynarmicCallbacks64>(memory, local_memory, mapped_ranges, *this);
+ user_config.callbacks = callbacks.get();
+ jit = std::make_unique<Dynarmic::A64::Jit>(user_config);
+ }
+
+ bool LoadNRO(std::span<const u8> data) {
+ local_memory.clear();
+ local_memory.insert(local_memory.end(), data.begin(), data.end());
+
+ if (FixupRelocations()) {
+ InsertHelperFunctions();
+ InsertStack();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ bool FixupRelocations() {
+ const VAddr mod_offset{callbacks->MemoryRead32(4)};
+ if (callbacks->MemoryRead32(mod_offset) != Common::MakeMagic('M', 'O', 'D', '0')) {
+ return false;
+ }
+
+ VAddr dynamic_offset{mod_offset + callbacks->MemoryRead32(mod_offset + 4)};
+ VAddr rela_dyn = 0;
+ size_t num_rela = 0;
+ while (true) {
+ const auto dyn{callbacks->ReadMemory<Elf64_Dyn>(dynamic_offset)};
+ dynamic_offset += sizeof(Elf64_Dyn);
+
+ if (!dyn.d_tag) {
+ break;
+ }
+ if (dyn.d_tag == DT_RELA) {
+ rela_dyn = dyn.d_un;
+ }
+ if (dyn.d_tag == DT_RELASZ) {
+ num_rela = dyn.d_un / sizeof(Elf64_Rela);
+ }
+ }
+
+ for (size_t i = 0; i < num_rela; i++) {
+ const auto rela{callbacks->ReadMemory<Elf64_Rela>(rela_dyn + i * sizeof(Elf64_Rela))};
+ if (Elf64_RelaType(&rela) != R_AARCH64_RELATIVE) {
+ continue;
+ }
+ const VAddr contents{callbacks->MemoryRead64(rela.r_offset)};
+ callbacks->MemoryWrite64(rela.r_offset, contents + rela.r_addend);
+ }
+
+ return true;
+ }
+
+ void InsertHelperFunctions() {
+ for (const auto& [name, contents] : HELPER_FUNCTIONS) {
+ helpers[name] = local_memory.size();
+ local_memory.insert(local_memory.end(), contents.begin(), contents.end());
+ }
+ }
+
+ void InsertStack() {
+ const u64 pad_amount{Common::AlignUp(local_memory.size(), STACK_ALIGN) -
+ local_memory.size()};
+ local_memory.insert(local_memory.end(), 0x10000 + pad_amount, 0);
+ top_of_stack = local_memory.size();
+ heap_pointer = top_of_stack;
+ }
+
+ void MapProcessMemory(VAddr dest_address, std::size_t size) {
+ mapped_ranges.add(IntervalType{dest_address, dest_address + size});
+ }
+
+ void PushArgument(const void* data, size_t size) {
+ const size_t num_words = Common::DivCeil(size, sizeof(u64));
+ const size_t current_pos = argument_stack.size();
+ argument_stack.insert(argument_stack.end(), num_words, 0);
+ std::memcpy(argument_stack.data() + current_pos, data, size);
+ }
+
+ void SetupArguments() {
+ for (size_t i = 0; i < 8 && i < argument_stack.size(); i++) {
+ jit->SetRegister(i, argument_stack[i]);
+ }
+ if (argument_stack.size() > 8) {
+ const VAddr new_sp = Common::AlignDown(
+ top_of_stack - (argument_stack.size() - 8) * sizeof(u64), STACK_ALIGN);
+ for (size_t i = 8; i < argument_stack.size(); i++) {
+ callbacks->MemoryWrite64(new_sp + (i - 8) * sizeof(u64), argument_stack[i]);
+ }
+ jit->SetSP(new_sp);
+ }
+ argument_stack.clear();
+ heap_pointer = top_of_stack;
+ }
+
+ u64 CallFunction(VAddr func) {
+ jit->SetRegister(30, helpers["_stop"]);
+ jit->SetSP(top_of_stack);
+ SetupArguments();
+
+ jit->SetPC(func);
+ jit->Run();
+ return jit->GetRegister(0);
+ }
+
+ VAddr GetHelper(const std::string& name) {
+ return helpers[name];
+ }
+
+ VAddr AddHeap(const void* data, size_t size) {
+ const size_t num_bytes{Common::AlignUp(size, STACK_ALIGN)};
+ if (heap_pointer + num_bytes > local_memory.size()) {
+ local_memory.insert(local_memory.end(),
+ (heap_pointer + num_bytes) - local_memory.size(), 0);
+ }
+ const VAddr location{heap_pointer};
+ std::memcpy(local_memory.data() + location, data, size);
+ heap_pointer += num_bytes;
+ return location;
+ }
+
+ void GetHeap(VAddr location, void* data, size_t size) {
+ std::memcpy(data, local_memory.data() + location, size);
+ }
+
+ std::unique_ptr<DynarmicCallbacks64> callbacks;
+ std::vector<u8> local_memory;
+ std::vector<u64> argument_stack;
+ IntervalSet mapped_ranges;
+ Dynarmic::A64::UserConfig user_config;
+ std::unique_ptr<Dynarmic::A64::Jit> jit;
+ std::map<std::string, VAddr, std::less<>> helpers;
+ Core::Memory::Memory& memory;
+ VAddr top_of_stack;
+ VAddr heap_pointer;
+};
+
+void DynarmicCallbacks64::CallSVC(u32 swi) {
+ switch (swi) {
+ case 0:
+ parent.jit->HaltExecution();
+ break;
+
+ case 1: {
+ // X0 contains a char* for a symbol to resolve
+ std::string name{MemoryReadCString(parent.jit->GetRegister(0))};
+ const auto helper{parent.helpers[name]};
+
+ if (helper != 0) {
+ parent.jit->SetRegister(0, helper);
+ } else {
+ LOG_WARNING(Service_JIT, "plugin requested unknown function {}", name);
+ parent.jit->SetRegister(0, parent.helpers["_panic"]);
+ }
+ break;
+ }
+
+ case 2:
+ default:
+ LOG_CRITICAL(Service_JIT, "plugin panicked!");
+ parent.jit->HaltExecution();
+ break;
+ }
+}
+
+void DynarmicCallbacks64::ExceptionRaised(u64 pc, Dynarmic::A64::Exception exception) {
+ LOG_CRITICAL(Service_JIT, "Illegal operation PC @ {:08x}", pc);
+ parent.jit->HaltExecution();
+}
+
+void DynarmicCallbacks64::InterpreterFallback(u64 pc, size_t num_instructions) {
+ LOG_CRITICAL(Service_JIT, "Unimplemented instruction PC @ {:08x}", pc);
+ parent.jit->HaltExecution();
+}
+
+JITContext::JITContext(Core::Memory::Memory& memory)
+ : impl{std::make_unique<JITContextImpl>(memory)} {}
+
+JITContext::~JITContext() {}
+
+bool JITContext::LoadNRO(std::span<const u8> data) {
+ return impl->LoadNRO(data);
+}
+
+void JITContext::MapProcessMemory(VAddr dest_address, std::size_t size) {
+ impl->MapProcessMemory(dest_address, size);
+}
+
+u64 JITContext::CallFunction(VAddr func) {
+ return impl->CallFunction(func);
+}
+
+void JITContext::PushArgument(const void* data, size_t size) {
+ impl->PushArgument(data, size);
+}
+
+VAddr JITContext::GetHelper(const std::string& name) {
+ return impl->GetHelper(name);
+}
+
+VAddr JITContext::AddHeap(const void* data, size_t size) {
+ return impl->AddHeap(data, size);
+}
+
+void JITContext::GetHeap(VAddr location, void* data, size_t size) {
+ impl->GetHeap(location, data, size);
+}
+
+} // namespace Service::JIT
diff --git a/src/core/hle/service/jit/jit_context.h b/src/core/hle/service/jit/jit_context.h
new file mode 100644
index 000000000..d8bf76cff
--- /dev/null
+++ b/src/core/hle/service/jit/jit_context.h
@@ -0,0 +1,65 @@
+// Copyright 2022 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <span>
+#include <string>
+
+#include "common/common_types.h"
+
+namespace Core::Memory {
+class Memory;
+}
+
+namespace Service::JIT {
+
+class JITContextImpl;
+
+class JITContext {
+public:
+ explicit JITContext(Core::Memory::Memory& memory);
+ ~JITContext();
+
+ [[nodiscard]] bool LoadNRO(std::span<const u8> data);
+ void MapProcessMemory(VAddr dest_address, std::size_t size);
+
+ template <typename T, typename... Ts>
+ u64 CallFunction(VAddr func, T argument, Ts... rest) {
+ static_assert(std::is_trivially_copyable_v<T>);
+ PushArgument(&argument, sizeof(argument));
+
+ if constexpr (sizeof...(rest) > 0) {
+ return CallFunction(func, rest...);
+ } else {
+ return CallFunction(func);
+ }
+ }
+
+ u64 CallFunction(VAddr func);
+ VAddr GetHelper(const std::string& name);
+
+ template <typename T>
+ VAddr AddHeap(T argument) {
+ return AddHeap(&argument, sizeof(argument));
+ }
+ VAddr AddHeap(const void* data, size_t size);
+
+ template <typename T>
+ T GetHeap(VAddr location) {
+ static_assert(std::is_trivially_copyable_v<T>);
+ T result;
+ GetHeap(location, &result, sizeof(result));
+ return result;
+ }
+ void GetHeap(VAddr location, void* data, size_t size);
+
+private:
+ std::unique_ptr<JITContextImpl> impl;
+
+ void PushArgument(const void* data, size_t size);
+};
+
+} // namespace Service::JIT
diff --git a/src/core/hle/service/ldr/ldr.cpp b/src/core/hle/service/ldr/ldr.cpp
index 2477c5612..42f9cf811 100644
--- a/src/core/hle/service/ldr/ldr.cpp
+++ b/src/core/hle/service/ldr/ldr.cpp
@@ -160,7 +160,8 @@ public:
class RelocatableObject final : public ServiceFramework<RelocatableObject> {
public:
- explicit RelocatableObject(Core::System& system_) : ServiceFramework{system_, "ldr:ro"} {
+ explicit RelocatableObject(Core::System& system_)
+ : ServiceFramework{system_, "ldr:ro", ServiceThreadType::CreateNew} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &RelocatableObject::LoadModule, "LoadModule"},
@@ -389,8 +390,12 @@ public:
if (bss_size) {
auto block_guard = detail::ScopeExit([&] {
- page_table.UnmapCodeMemory(addr + nro_size, bss_addr, bss_size);
- page_table.UnmapCodeMemory(addr, nro_addr, nro_size);
+ page_table.UnmapCodeMemory(
+ addr + nro_size, bss_addr, bss_size,
+ Kernel::KPageTable::ICacheInvalidationStrategy::InvalidateRange);
+ page_table.UnmapCodeMemory(
+ addr, nro_addr, nro_size,
+ Kernel::KPageTable::ICacheInvalidationStrategy::InvalidateRange);
});
const ResultCode result{
@@ -570,17 +575,21 @@ public:
auto& page_table{system.CurrentProcess()->PageTable()};
if (info.bss_size != 0) {
- CASCADE_CODE(page_table.UnmapCodeMemory(info.nro_address + info.text_size +
- info.ro_size + info.data_size,
- info.bss_address, info.bss_size));
+ CASCADE_CODE(page_table.UnmapCodeMemory(
+ info.nro_address + info.text_size + info.ro_size + info.data_size, info.bss_address,
+ info.bss_size, Kernel::KPageTable::ICacheInvalidationStrategy::InvalidateRange));
}
- CASCADE_CODE(page_table.UnmapCodeMemory(info.nro_address + info.text_size + info.ro_size,
- info.src_addr + info.text_size + info.ro_size,
- info.data_size));
- CASCADE_CODE(page_table.UnmapCodeMemory(info.nro_address + info.text_size,
- info.src_addr + info.text_size, info.ro_size));
- CASCADE_CODE(page_table.UnmapCodeMemory(info.nro_address, info.src_addr, info.text_size));
+ CASCADE_CODE(page_table.UnmapCodeMemory(
+ info.nro_address + info.text_size + info.ro_size,
+ info.src_addr + info.text_size + info.ro_size, info.data_size,
+ Kernel::KPageTable::ICacheInvalidationStrategy::InvalidateRange));
+ CASCADE_CODE(page_table.UnmapCodeMemory(
+ info.nro_address + info.text_size, info.src_addr + info.text_size, info.ro_size,
+ Kernel::KPageTable::ICacheInvalidationStrategy::InvalidateRange));
+ CASCADE_CODE(page_table.UnmapCodeMemory(
+ info.nro_address, info.src_addr, info.text_size,
+ Kernel::KPageTable::ICacheInvalidationStrategy::InvalidateRange));
return ResultSuccess;
}
diff --git a/src/core/hle/service/mii/mii_manager.cpp b/src/core/hle/service/mii/mii_manager.cpp
index 0a57c3cde..188231615 100644
--- a/src/core/hle/service/mii/mii_manager.cpp
+++ b/src/core/hle/service/mii/mii_manager.cpp
@@ -1,4 +1,4 @@
-// Copyright 2020 yuzu emulator team
+// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -12,7 +12,6 @@
#include "core/hle/service/acc/profile_manager.h"
#include "core/hle/service/mii/mii_manager.h"
#include "core/hle/service/mii/raw_data.h"
-#include "core/hle/service/mii/types.h"
namespace Service::Mii {
diff --git a/src/core/hle/service/mii/mii_manager.h b/src/core/hle/service/mii/mii_manager.h
index 6999d15b1..5d134c425 100644
--- a/src/core/hle/service/mii/mii_manager.h
+++ b/src/core/hle/service/mii/mii_manager.h
@@ -1,315 +1,16 @@
-// Copyright 2020 yuzu emulator team
+// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
-#include <array>
#include <vector>
-#include "common/bit_field.h"
-#include "common/common_funcs.h"
-#include "common/uuid.h"
+
#include "core/hle/result.h"
#include "core/hle/service/mii/types.h"
namespace Service::Mii {
-enum class Source : u32 {
- Database = 0,
- Default = 1,
- Account = 2,
- Friend = 3,
-};
-
-enum class SourceFlag : u32 {
- None = 0,
- Database = 1 << 0,
- Default = 1 << 1,
-};
-DECLARE_ENUM_FLAG_OPERATORS(SourceFlag);
-
-struct MiiInfo {
- Common::UUID uuid;
- std::array<char16_t, 11> name;
- u8 font_region;
- u8 favorite_color;
- u8 gender;
- u8 height;
- u8 build;
- u8 type;
- u8 region_move;
- u8 faceline_type;
- u8 faceline_color;
- u8 faceline_wrinkle;
- u8 faceline_make;
- u8 hair_type;
- u8 hair_color;
- u8 hair_flip;
- u8 eye_type;
- u8 eye_color;
- u8 eye_scale;
- u8 eye_aspect;
- u8 eye_rotate;
- u8 eye_x;
- u8 eye_y;
- u8 eyebrow_type;
- u8 eyebrow_color;
- u8 eyebrow_scale;
- u8 eyebrow_aspect;
- u8 eyebrow_rotate;
- u8 eyebrow_x;
- u8 eyebrow_y;
- u8 nose_type;
- u8 nose_scale;
- u8 nose_y;
- u8 mouth_type;
- u8 mouth_color;
- u8 mouth_scale;
- u8 mouth_aspect;
- u8 mouth_y;
- u8 beard_color;
- u8 beard_type;
- u8 mustache_type;
- u8 mustache_scale;
- u8 mustache_y;
- u8 glasses_type;
- u8 glasses_color;
- u8 glasses_scale;
- u8 glasses_y;
- u8 mole_type;
- u8 mole_scale;
- u8 mole_x;
- u8 mole_y;
- u8 padding;
-
- std::u16string Name() const;
-};
-static_assert(sizeof(MiiInfo) == 0x58, "MiiInfo has incorrect size.");
-static_assert(std::has_unique_object_representations_v<MiiInfo>,
- "All bits of MiiInfo must contribute to its value.");
-
-#pragma pack(push, 4)
-
-struct MiiInfoElement {
- MiiInfoElement(const MiiInfo& info_, Source source_) : info{info_}, source{source_} {}
-
- MiiInfo info{};
- Source source{};
-};
-static_assert(sizeof(MiiInfoElement) == 0x5c, "MiiInfoElement has incorrect size.");
-
-struct MiiStoreBitFields {
- union {
- u32 word_0{};
-
- BitField<0, 8, u32> hair_type;
- BitField<8, 7, u32> height;
- BitField<15, 1, u32> mole_type;
- BitField<16, 7, u32> build;
- BitField<23, 1, HairFlip> hair_flip;
- BitField<24, 7, u32> hair_color;
- BitField<31, 1, u32> type;
- };
-
- union {
- u32 word_1{};
-
- BitField<0, 7, u32> eye_color;
- BitField<7, 1, Gender> gender;
- BitField<8, 7, u32> eyebrow_color;
- BitField<16, 7, u32> mouth_color;
- BitField<24, 7, u32> beard_color;
- };
-
- union {
- u32 word_2{};
-
- BitField<0, 7, u32> glasses_color;
- BitField<8, 6, u32> eye_type;
- BitField<14, 2, u32> region_move;
- BitField<16, 6, u32> mouth_type;
- BitField<22, 2, FontRegion> font_region;
- BitField<24, 5, u32> eye_y;
- BitField<29, 3, u32> glasses_scale;
- };
-
- union {
- u32 word_3{};
-
- BitField<0, 5, u32> eyebrow_type;
- BitField<5, 3, MustacheType> mustache_type;
- BitField<8, 5, u32> nose_type;
- BitField<13, 3, BeardType> beard_type;
- BitField<16, 5, u32> nose_y;
- BitField<21, 3, u32> mouth_aspect;
- BitField<24, 5, u32> mouth_y;
- BitField<29, 3, u32> eyebrow_aspect;
- };
-
- union {
- u32 word_4{};
-
- BitField<0, 5, u32> mustache_y;
- BitField<5, 3, u32> eye_rotate;
- BitField<8, 5, u32> glasses_y;
- BitField<13, 3, u32> eye_aspect;
- BitField<16, 5, u32> mole_x;
- BitField<21, 3, u32> eye_scale;
- BitField<24, 5, u32> mole_y;
- };
-
- union {
- u32 word_5{};
-
- BitField<0, 5, u32> glasses_type;
- BitField<8, 4, u32> favorite_color;
- BitField<12, 4, u32> faceline_type;
- BitField<16, 4, u32> faceline_color;
- BitField<20, 4, u32> faceline_wrinkle;
- BitField<24, 4, u32> faceline_makeup;
- BitField<28, 4, u32> eye_x;
- };
-
- union {
- u32 word_6{};
-
- BitField<0, 4, u32> eyebrow_scale;
- BitField<4, 4, u32> eyebrow_rotate;
- BitField<8, 4, u32> eyebrow_x;
- BitField<12, 4, u32> eyebrow_y;
- BitField<16, 4, u32> nose_scale;
- BitField<20, 4, u32> mouth_scale;
- BitField<24, 4, u32> mustache_scale;
- BitField<28, 4, u32> mole_scale;
- };
-};
-static_assert(sizeof(MiiStoreBitFields) == 0x1c, "MiiStoreBitFields has incorrect size.");
-static_assert(std::is_trivially_copyable_v<MiiStoreBitFields>,
- "MiiStoreBitFields is not trivially copyable.");
-
-struct MiiStoreData {
- using Name = std::array<char16_t, 10>;
-
- MiiStoreData();
- MiiStoreData(const Name& name, const MiiStoreBitFields& bit_fields,
- const Common::UUID& user_id);
-
- // This corresponds to the above structure MiiStoreBitFields. I did it like this because the
- // BitField<> type makes this (and any thing that contains it) not trivially copyable, which is
- // not suitable for our uses.
- struct {
- std::array<u8, 0x1C> data{};
- static_assert(sizeof(MiiStoreBitFields) == sizeof(data), "data field has incorrect size.");
-
- Name name{};
- Common::UUID uuid{};
- } data;
-
- u16 data_crc{};
- u16 device_crc{};
-};
-static_assert(sizeof(MiiStoreData) == 0x44, "MiiStoreData has incorrect size.");
-
-struct MiiStoreDataElement {
- MiiStoreData data{};
- Source source{};
-};
-static_assert(sizeof(MiiStoreDataElement) == 0x48, "MiiStoreDataElement has incorrect size.");
-
-struct MiiDatabase {
- u32 magic{}; // 'NFDB'
- std::array<MiiStoreData, 0x64> miis{};
- INSERT_PADDING_BYTES(1);
- u8 count{};
- u16 crc{};
-};
-static_assert(sizeof(MiiDatabase) == 0x1A98, "MiiDatabase has incorrect size.");
-
-struct RandomMiiValues {
- std::array<u8, 0xbc> values{};
-};
-static_assert(sizeof(RandomMiiValues) == 0xbc, "RandomMiiValues has incorrect size.");
-
-struct RandomMiiData4 {
- Gender gender{};
- Age age{};
- Race race{};
- u32 values_count{};
- std::array<u32, 47> values{};
-};
-static_assert(sizeof(RandomMiiData4) == 0xcc, "RandomMiiData4 has incorrect size.");
-
-struct RandomMiiData3 {
- u32 arg_1;
- u32 arg_2;
- u32 values_count;
- std::array<u32, 47> values{};
-};
-static_assert(sizeof(RandomMiiData3) == 0xc8, "RandomMiiData3 has incorrect size.");
-
-struct RandomMiiData2 {
- u32 arg_1;
- u32 values_count;
- std::array<u32, 47> values{};
-};
-static_assert(sizeof(RandomMiiData2) == 0xc4, "RandomMiiData2 has incorrect size.");
-
-struct DefaultMii {
- u32 face_type{};
- u32 face_color{};
- u32 face_wrinkle{};
- u32 face_makeup{};
- u32 hair_type{};
- u32 hair_color{};
- u32 hair_flip{};
- u32 eye_type{};
- u32 eye_color{};
- u32 eye_scale{};
- u32 eye_aspect{};
- u32 eye_rotate{};
- u32 eye_x{};
- u32 eye_y{};
- u32 eyebrow_type{};
- u32 eyebrow_color{};
- u32 eyebrow_scale{};
- u32 eyebrow_aspect{};
- u32 eyebrow_rotate{};
- u32 eyebrow_x{};
- u32 eyebrow_y{};
- u32 nose_type{};
- u32 nose_scale{};
- u32 nose_y{};
- u32 mouth_type{};
- u32 mouth_color{};
- u32 mouth_scale{};
- u32 mouth_aspect{};
- u32 mouth_y{};
- u32 mustache_type{};
- u32 beard_type{};
- u32 beard_color{};
- u32 mustache_scale{};
- u32 mustache_y{};
- u32 glasses_type{};
- u32 glasses_color{};
- u32 glasses_scale{};
- u32 glasses_y{};
- u32 mole_type{};
- u32 mole_scale{};
- u32 mole_x{};
- u32 mole_y{};
- u32 height{};
- u32 weight{};
- Gender gender{};
- u32 favorite_color{};
- u32 region{};
- FontRegion font_region{};
- u32 type{};
- INSERT_PADDING_WORDS(5);
-};
-static_assert(sizeof(DefaultMii) == 0xd8, "MiiStoreData has incorrect size.");
-
-#pragma pack(pop)
-
// The Mii manager is responsible for loading and storing the Miis to the database in NAND along
// with providing an easy interface for HLE emulation of the mii service.
class MiiManager {
diff --git a/src/core/hle/service/mii/raw_data.h b/src/core/hle/service/mii/raw_data.h
index bd90c2162..2e39c0d4f 100644
--- a/src/core/hle/service/mii/raw_data.h
+++ b/src/core/hle/service/mii/raw_data.h
@@ -6,7 +6,7 @@
#include <array>
-#include "core/hle/service/mii/mii_manager.h"
+#include "core/hle/service/mii/types.h"
namespace Service::Mii::RawData {
diff --git a/src/core/hle/service/mii/types.h b/src/core/hle/service/mii/types.h
index d65a1055e..5580b8c6a 100644
--- a/src/core/hle/service/mii/types.h
+++ b/src/core/hle/service/mii/types.h
@@ -1,11 +1,16 @@
-// Copyright 2020 yuzu emulator team
+// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
+#include <array>
+#include <type_traits>
+
+#include "common/bit_field.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
+#include "common/uuid.h"
namespace Service::Mii {
@@ -25,7 +30,11 @@ enum class BeardType : u32 {
Beard5,
};
-enum class BeardAndMustacheFlag : u32 { Beard = 1, Mustache, All = Beard | Mustache };
+enum class BeardAndMustacheFlag : u32 {
+ Beard = 1,
+ Mustache,
+ All = Beard | Mustache,
+};
DECLARE_ENUM_FLAG_OPERATORS(BeardAndMustacheFlag);
enum class FontRegion : u32 {
@@ -64,4 +73,298 @@ enum class Race : u32 {
All,
};
+enum class Source : u32 {
+ Database = 0,
+ Default = 1,
+ Account = 2,
+ Friend = 3,
+};
+
+enum class SourceFlag : u32 {
+ None = 0,
+ Database = 1 << 0,
+ Default = 1 << 1,
+};
+DECLARE_ENUM_FLAG_OPERATORS(SourceFlag);
+
+struct MiiInfo {
+ Common::UUID uuid;
+ std::array<char16_t, 11> name;
+ u8 font_region;
+ u8 favorite_color;
+ u8 gender;
+ u8 height;
+ u8 build;
+ u8 type;
+ u8 region_move;
+ u8 faceline_type;
+ u8 faceline_color;
+ u8 faceline_wrinkle;
+ u8 faceline_make;
+ u8 hair_type;
+ u8 hair_color;
+ u8 hair_flip;
+ u8 eye_type;
+ u8 eye_color;
+ u8 eye_scale;
+ u8 eye_aspect;
+ u8 eye_rotate;
+ u8 eye_x;
+ u8 eye_y;
+ u8 eyebrow_type;
+ u8 eyebrow_color;
+ u8 eyebrow_scale;
+ u8 eyebrow_aspect;
+ u8 eyebrow_rotate;
+ u8 eyebrow_x;
+ u8 eyebrow_y;
+ u8 nose_type;
+ u8 nose_scale;
+ u8 nose_y;
+ u8 mouth_type;
+ u8 mouth_color;
+ u8 mouth_scale;
+ u8 mouth_aspect;
+ u8 mouth_y;
+ u8 beard_color;
+ u8 beard_type;
+ u8 mustache_type;
+ u8 mustache_scale;
+ u8 mustache_y;
+ u8 glasses_type;
+ u8 glasses_color;
+ u8 glasses_scale;
+ u8 glasses_y;
+ u8 mole_type;
+ u8 mole_scale;
+ u8 mole_x;
+ u8 mole_y;
+ u8 padding;
+};
+static_assert(sizeof(MiiInfo) == 0x58, "MiiInfo has incorrect size.");
+static_assert(std::has_unique_object_representations_v<MiiInfo>,
+ "All bits of MiiInfo must contribute to its value.");
+
+#pragma pack(push, 4)
+
+struct MiiInfoElement {
+ MiiInfoElement(const MiiInfo& info_, Source source_) : info{info_}, source{source_} {}
+
+ MiiInfo info{};
+ Source source{};
+};
+static_assert(sizeof(MiiInfoElement) == 0x5c, "MiiInfoElement has incorrect size.");
+
+struct MiiStoreBitFields {
+ union {
+ u32 word_0{};
+
+ BitField<0, 8, u32> hair_type;
+ BitField<8, 7, u32> height;
+ BitField<15, 1, u32> mole_type;
+ BitField<16, 7, u32> build;
+ BitField<23, 1, HairFlip> hair_flip;
+ BitField<24, 7, u32> hair_color;
+ BitField<31, 1, u32> type;
+ };
+
+ union {
+ u32 word_1{};
+
+ BitField<0, 7, u32> eye_color;
+ BitField<7, 1, Gender> gender;
+ BitField<8, 7, u32> eyebrow_color;
+ BitField<16, 7, u32> mouth_color;
+ BitField<24, 7, u32> beard_color;
+ };
+
+ union {
+ u32 word_2{};
+
+ BitField<0, 7, u32> glasses_color;
+ BitField<8, 6, u32> eye_type;
+ BitField<14, 2, u32> region_move;
+ BitField<16, 6, u32> mouth_type;
+ BitField<22, 2, FontRegion> font_region;
+ BitField<24, 5, u32> eye_y;
+ BitField<29, 3, u32> glasses_scale;
+ };
+
+ union {
+ u32 word_3{};
+
+ BitField<0, 5, u32> eyebrow_type;
+ BitField<5, 3, MustacheType> mustache_type;
+ BitField<8, 5, u32> nose_type;
+ BitField<13, 3, BeardType> beard_type;
+ BitField<16, 5, u32> nose_y;
+ BitField<21, 3, u32> mouth_aspect;
+ BitField<24, 5, u32> mouth_y;
+ BitField<29, 3, u32> eyebrow_aspect;
+ };
+
+ union {
+ u32 word_4{};
+
+ BitField<0, 5, u32> mustache_y;
+ BitField<5, 3, u32> eye_rotate;
+ BitField<8, 5, u32> glasses_y;
+ BitField<13, 3, u32> eye_aspect;
+ BitField<16, 5, u32> mole_x;
+ BitField<21, 3, u32> eye_scale;
+ BitField<24, 5, u32> mole_y;
+ };
+
+ union {
+ u32 word_5{};
+
+ BitField<0, 5, u32> glasses_type;
+ BitField<8, 4, u32> favorite_color;
+ BitField<12, 4, u32> faceline_type;
+ BitField<16, 4, u32> faceline_color;
+ BitField<20, 4, u32> faceline_wrinkle;
+ BitField<24, 4, u32> faceline_makeup;
+ BitField<28, 4, u32> eye_x;
+ };
+
+ union {
+ u32 word_6{};
+
+ BitField<0, 4, u32> eyebrow_scale;
+ BitField<4, 4, u32> eyebrow_rotate;
+ BitField<8, 4, u32> eyebrow_x;
+ BitField<12, 4, u32> eyebrow_y;
+ BitField<16, 4, u32> nose_scale;
+ BitField<20, 4, u32> mouth_scale;
+ BitField<24, 4, u32> mustache_scale;
+ BitField<28, 4, u32> mole_scale;
+ };
+};
+static_assert(sizeof(MiiStoreBitFields) == 0x1c, "MiiStoreBitFields has incorrect size.");
+static_assert(std::is_trivially_copyable_v<MiiStoreBitFields>,
+ "MiiStoreBitFields is not trivially copyable.");
+
+struct MiiStoreData {
+ using Name = std::array<char16_t, 10>;
+
+ MiiStoreData();
+ MiiStoreData(const Name& name, const MiiStoreBitFields& bit_fields,
+ const Common::UUID& user_id);
+
+ // This corresponds to the above structure MiiStoreBitFields. I did it like this because the
+ // BitField<> type makes this (and any thing that contains it) not trivially copyable, which is
+ // not suitable for our uses.
+ struct {
+ std::array<u8, 0x1C> data{};
+ static_assert(sizeof(MiiStoreBitFields) == sizeof(data), "data field has incorrect size.");
+
+ Name name{};
+ Common::UUID uuid{};
+ } data;
+
+ u16 data_crc{};
+ u16 device_crc{};
+};
+static_assert(sizeof(MiiStoreData) == 0x44, "MiiStoreData has incorrect size.");
+
+struct MiiStoreDataElement {
+ MiiStoreData data{};
+ Source source{};
+};
+static_assert(sizeof(MiiStoreDataElement) == 0x48, "MiiStoreDataElement has incorrect size.");
+
+struct MiiDatabase {
+ u32 magic{}; // 'NFDB'
+ std::array<MiiStoreData, 0x64> miis{};
+ INSERT_PADDING_BYTES(1);
+ u8 count{};
+ u16 crc{};
+};
+static_assert(sizeof(MiiDatabase) == 0x1A98, "MiiDatabase has incorrect size.");
+
+struct RandomMiiValues {
+ std::array<u8, 0xbc> values{};
+};
+static_assert(sizeof(RandomMiiValues) == 0xbc, "RandomMiiValues has incorrect size.");
+
+struct RandomMiiData4 {
+ Gender gender{};
+ Age age{};
+ Race race{};
+ u32 values_count{};
+ std::array<u32, 47> values{};
+};
+static_assert(sizeof(RandomMiiData4) == 0xcc, "RandomMiiData4 has incorrect size.");
+
+struct RandomMiiData3 {
+ u32 arg_1;
+ u32 arg_2;
+ u32 values_count;
+ std::array<u32, 47> values{};
+};
+static_assert(sizeof(RandomMiiData3) == 0xc8, "RandomMiiData3 has incorrect size.");
+
+struct RandomMiiData2 {
+ u32 arg_1;
+ u32 values_count;
+ std::array<u32, 47> values{};
+};
+static_assert(sizeof(RandomMiiData2) == 0xc4, "RandomMiiData2 has incorrect size.");
+
+struct DefaultMii {
+ u32 face_type{};
+ u32 face_color{};
+ u32 face_wrinkle{};
+ u32 face_makeup{};
+ u32 hair_type{};
+ u32 hair_color{};
+ u32 hair_flip{};
+ u32 eye_type{};
+ u32 eye_color{};
+ u32 eye_scale{};
+ u32 eye_aspect{};
+ u32 eye_rotate{};
+ u32 eye_x{};
+ u32 eye_y{};
+ u32 eyebrow_type{};
+ u32 eyebrow_color{};
+ u32 eyebrow_scale{};
+ u32 eyebrow_aspect{};
+ u32 eyebrow_rotate{};
+ u32 eyebrow_x{};
+ u32 eyebrow_y{};
+ u32 nose_type{};
+ u32 nose_scale{};
+ u32 nose_y{};
+ u32 mouth_type{};
+ u32 mouth_color{};
+ u32 mouth_scale{};
+ u32 mouth_aspect{};
+ u32 mouth_y{};
+ u32 mustache_type{};
+ u32 beard_type{};
+ u32 beard_color{};
+ u32 mustache_scale{};
+ u32 mustache_y{};
+ u32 glasses_type{};
+ u32 glasses_color{};
+ u32 glasses_scale{};
+ u32 glasses_y{};
+ u32 mole_type{};
+ u32 mole_scale{};
+ u32 mole_x{};
+ u32 mole_y{};
+ u32 height{};
+ u32 weight{};
+ Gender gender{};
+ u32 favorite_color{};
+ u32 region{};
+ FontRegion font_region{};
+ u32 type{};
+ INSERT_PADDING_WORDS(5);
+};
+static_assert(sizeof(DefaultMii) == 0xd8, "MiiStoreData has incorrect size.");
+
+#pragma pack(pop)
+
} // namespace Service::Mii
diff --git a/src/core/hle/service/nfp/nfp.cpp b/src/core/hle/service/nfp/nfp.cpp
index 513107715..dab99b675 100644
--- a/src/core/hle/service/nfp/nfp.cpp
+++ b/src/core/hle/service/nfp/nfp.cpp
@@ -12,6 +12,7 @@
#include "core/hid/hid_types.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/k_event.h"
+#include "core/hle/service/mii/mii_manager.h"
#include "core/hle/service/nfp/nfp.h"
#include "core/hle/service/nfp/nfp_user.h"
diff --git a/src/core/hle/service/nfp/nfp.h b/src/core/hle/service/nfp/nfp.h
index 022f13b29..ab652f635 100644
--- a/src/core/hle/service/nfp/nfp.h
+++ b/src/core/hle/service/nfp/nfp.h
@@ -9,7 +9,7 @@
#include "common/common_funcs.h"
#include "core/hle/service/kernel_helpers.h"
-#include "core/hle/service/mii/mii_manager.h"
+#include "core/hle/service/mii/types.h"
#include "core/hle/service/service.h"
namespace Kernel {
diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp
index 68f1e9060..9fad45fe1 100644
--- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp
@@ -38,18 +38,16 @@ NvResult nvdisp_disp0::Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>&
void nvdisp_disp0::OnOpen(DeviceFD fd) {}
void nvdisp_disp0::OnClose(DeviceFD fd) {}
-void nvdisp_disp0::flip(u32 buffer_handle, u32 offset, u32 format, u32 width, u32 height,
- u32 stride, NVFlinger::BufferQueue::BufferTransformFlags transform,
+void nvdisp_disp0::flip(u32 buffer_handle, u32 offset, android::PixelFormat format, u32 width,
+ u32 height, u32 stride, android::BufferTransformFlags transform,
const Common::Rectangle<int>& crop_rect) {
const VAddr addr = nvmap_dev->GetObjectAddress(buffer_handle);
LOG_TRACE(Service,
"Drawing from address {:X} offset {:08X} Width {} Height {} Stride {} Format {}",
addr, offset, width, height, stride, format);
- const auto pixel_format = static_cast<Tegra::FramebufferConfig::PixelFormat>(format);
- const auto transform_flags = static_cast<Tegra::FramebufferConfig::TransformFlags>(transform);
- const Tegra::FramebufferConfig framebuffer{addr, offset, width, height,
- stride, pixel_format, transform_flags, crop_rect};
+ const Tegra::FramebufferConfig framebuffer{addr, offset, width, height,
+ stride, format, transform, crop_rect};
system.GetPerfStats().EndSystemFrame();
system.GPU().SwapBuffers(&framebuffer);
diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h
index de01e1d5f..30b5da429 100644
--- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h
+++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h
@@ -9,7 +9,8 @@
#include "common/common_types.h"
#include "common/math_util.h"
#include "core/hle/service/nvdrv/devices/nvdevice.h"
-#include "core/hle/service/nvflinger/buffer_queue.h"
+#include "core/hle/service/nvflinger/buffer_transform_flags.h"
+#include "core/hle/service/nvflinger/pixel_format.h"
namespace Service::Nvidia::Devices {
@@ -31,8 +32,8 @@ public:
void OnClose(DeviceFD fd) override;
/// Performs a screen flip, drawing the buffer pointed to by the handle.
- void flip(u32 buffer_handle, u32 offset, u32 format, u32 width, u32 height, u32 stride,
- NVFlinger::BufferQueue::BufferTransformFlags transform,
+ void flip(u32 buffer_handle, u32 offset, android::PixelFormat format, u32 width, u32 height,
+ u32 stride, android::BufferTransformFlags transform,
const Common::Rectangle<int>& crop_rect);
private:
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp
index f9b82b504..44c54c665 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp
@@ -134,7 +134,7 @@ NvResult nvhost_ctrl::IocCtrlEventWait(const std::vector<u8>& input, std::vector
}
EventState status = events_interface.status[event_id];
- const bool bad_parameter = status != EventState::Free && status != EventState::Registered;
+ const bool bad_parameter = status == EventState::Busy;
if (bad_parameter) {
std::memcpy(output.data(), &params, sizeof(params));
return NvResult::BadParameter;
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
index 0a043e386..dde5b1507 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
@@ -187,7 +187,7 @@ NvResult nvhost_gpu::AllocateObjectContext(const std::vector<u8>& input, std::ve
return NvResult::Success;
}
-static std::vector<Tegra::CommandHeader> BuildWaitCommandList(Fence fence) {
+static std::vector<Tegra::CommandHeader> BuildWaitCommandList(NvFence fence) {
return {
Tegra::BuildCommandHeader(Tegra::BufferMethods::FenceValue, 1,
Tegra::SubmissionMode::Increasing),
@@ -198,7 +198,8 @@ static std::vector<Tegra::CommandHeader> BuildWaitCommandList(Fence fence) {
};
}
-static std::vector<Tegra::CommandHeader> BuildIncrementCommandList(Fence fence, u32 add_increment) {
+static std::vector<Tegra::CommandHeader> BuildIncrementCommandList(NvFence fence,
+ u32 add_increment) {
std::vector<Tegra::CommandHeader> result{
Tegra::BuildCommandHeader(Tegra::BufferMethods::FenceValue, 1,
Tegra::SubmissionMode::Increasing),
@@ -213,7 +214,7 @@ static std::vector<Tegra::CommandHeader> BuildIncrementCommandList(Fence fence,
return result;
}
-static std::vector<Tegra::CommandHeader> BuildIncrementWithWfiCommandList(Fence fence,
+static std::vector<Tegra::CommandHeader> BuildIncrementWithWfiCommandList(NvFence fence,
u32 add_increment) {
std::vector<Tegra::CommandHeader> result{
Tegra::BuildCommandHeader(Tegra::BufferMethods::WaitForInterrupt, 1,
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h
index 3e4f3b6a7..b2e943e45 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h
@@ -109,7 +109,7 @@ private:
static_assert(sizeof(IoctlGetErrorNotification) == 16,
"IoctlGetErrorNotification is incorrect size");
- static_assert(sizeof(Fence) == 8, "Fence is incorrect size");
+ static_assert(sizeof(NvFence) == 8, "Fence is incorrect size");
struct IoctlAllocGpfifoEx {
u32_le num_entries{};
@@ -127,7 +127,7 @@ private:
u32_le num_entries{}; // in
u32_le flags{}; // in
u32_le unk0{}; // in (1 works)
- Fence fence_out{}; // out
+ NvFence fence_out{}; // out
u32_le unk1{}; // in
u32_le unk2{}; // in
u32_le unk3{}; // in
@@ -153,13 +153,13 @@ private:
BitField<4, 1, u32_le> suppress_wfi; // suppress wait for interrupt
BitField<8, 1, u32_le> increment; // increment the returned fence
} flags;
- Fence fence_out{}; // returned new fence object for others to wait on
+ NvFence fence_out{}; // returned new fence object for others to wait on
u32 AddIncrementValue() const {
return flags.add_increment.Value() << 1;
}
};
- static_assert(sizeof(IoctlSubmitGpfifo) == 16 + sizeof(Fence),
+ static_assert(sizeof(IoctlSubmitGpfifo) == 16 + sizeof(NvFence),
"IoctlSubmitGpfifo is incorrect size");
struct IoctlGetWaitbase {
@@ -194,7 +194,7 @@ private:
std::shared_ptr<nvmap> nvmap_dev;
SyncpointManager& syncpoint_manager;
- Fence channel_fence;
+ NvFence channel_fence;
};
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/nvdata.h b/src/core/hle/service/nvdrv/nvdata.h
index 5ab221fc1..3069c3c80 100644
--- a/src/core/hle/service/nvdrv/nvdata.h
+++ b/src/core/hle/service/nvdrv/nvdata.h
@@ -16,17 +16,11 @@ using DeviceFD = s32;
constexpr DeviceFD INVALID_NVDRV_FD = -1;
-struct Fence {
+struct NvFence {
s32 id;
u32 value;
};
-
-static_assert(sizeof(Fence) == 8, "Fence has wrong size");
-
-struct MultiFence {
- u32 num_fences;
- std::array<Fence, 4> fences;
-};
+static_assert(sizeof(NvFence) == 8, "NvFence has wrong size");
enum class NvResult : u32 {
Success = 0x0,
diff --git a/src/core/hle/service/nvdrv/nvdrv.h b/src/core/hle/service/nvdrv/nvdrv.h
index a5af5b785..11cf6c0d1 100644
--- a/src/core/hle/service/nvdrv/nvdrv.h
+++ b/src/core/hle/service/nvdrv/nvdrv.h
@@ -12,6 +12,7 @@
#include "core/hle/service/kernel_helpers.h"
#include "core/hle/service/nvdrv/nvdata.h"
#include "core/hle/service/nvdrv/syncpoint_manager.h"
+#include "core/hle/service/nvflinger/ui/fence.h"
#include "core/hle/service/service.h"
namespace Core {
@@ -37,7 +38,7 @@ class nvdevice;
/// Represents an Nvidia event
struct NvEvent {
Kernel::KEvent* event{};
- Fence fence{};
+ NvFence fence{};
};
struct EventInterface {
diff --git a/src/core/hle/service/nvdrv/nvdrv_interface.cpp b/src/core/hle/service/nvdrv/nvdrv_interface.cpp
index c16babe14..8467b50e4 100644
--- a/src/core/hle/service/nvdrv/nvdrv_interface.cpp
+++ b/src/core/hle/service/nvdrv/nvdrv_interface.cpp
@@ -26,7 +26,7 @@ void NVDRV::Open(Kernel::HLERequestContext& ctx) {
rb.Push<DeviceFD>(0);
rb.PushEnum(NvResult::NotInitialized);
- LOG_ERROR(Service_NVDRV, "NvServices is not initalized!");
+ LOG_ERROR(Service_NVDRV, "NvServices is not initialized!");
return;
}
@@ -61,7 +61,7 @@ void NVDRV::Ioctl1(Kernel::HLERequestContext& ctx) {
if (!is_initialized) {
ServiceError(ctx, NvResult::NotInitialized);
- LOG_ERROR(Service_NVDRV, "NvServices is not initalized!");
+ LOG_ERROR(Service_NVDRV, "NvServices is not initialized!");
return;
}
@@ -87,7 +87,7 @@ void NVDRV::Ioctl2(Kernel::HLERequestContext& ctx) {
if (!is_initialized) {
ServiceError(ctx, NvResult::NotInitialized);
- LOG_ERROR(Service_NVDRV, "NvServices is not initalized!");
+ LOG_ERROR(Service_NVDRV, "NvServices is not initialized!");
return;
}
@@ -114,7 +114,7 @@ void NVDRV::Ioctl3(Kernel::HLERequestContext& ctx) {
if (!is_initialized) {
ServiceError(ctx, NvResult::NotInitialized);
- LOG_ERROR(Service_NVDRV, "NvServices is not initalized!");
+ LOG_ERROR(Service_NVDRV, "NvServices is not initialized!");
return;
}
@@ -139,7 +139,7 @@ void NVDRV::Close(Kernel::HLERequestContext& ctx) {
if (!is_initialized) {
ServiceError(ctx, NvResult::NotInitialized);
- LOG_ERROR(Service_NVDRV, "NvServices is not initalized!");
+ LOG_ERROR(Service_NVDRV, "NvServices is not initialized!");
return;
}
@@ -170,7 +170,7 @@ void NVDRV::QueryEvent(Kernel::HLERequestContext& ctx) {
if (!is_initialized) {
ServiceError(ctx, NvResult::NotInitialized);
- LOG_ERROR(Service_NVDRV, "NvServices is not initalized!");
+ LOG_ERROR(Service_NVDRV, "NvServices is not initialized!");
return;
}
@@ -230,7 +230,7 @@ void NVDRV::DumpGraphicsMemoryInfo(Kernel::HLERequestContext& ctx) {
}
NVDRV::NVDRV(Core::System& system_, std::shared_ptr<Module> nvdrv_, const char* name)
- : ServiceFramework{system_, name}, nvdrv{std::move(nvdrv_)} {
+ : ServiceFramework{system_, name, ServiceThreadType::CreateNew}, nvdrv{std::move(nvdrv_)} {
static const FunctionInfo functions[] = {
{0, &NVDRV::Open, "Open"},
{1, &NVDRV::Ioctl1, "Ioctl"},
diff --git a/src/core/hle/service/nvflinger/binder.h b/src/core/hle/service/nvflinger/binder.h
new file mode 100644
index 000000000..7d0d4d819
--- /dev/null
+++ b/src/core/hle/service/nvflinger/binder.h
@@ -0,0 +1,42 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Copyright 2021 yuzu Emulator Project
+// Copyright 2014 The Android Open Source Project
+// Parts of this implementation were base on:
+// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/binder/IBinder.h
+
+#pragma once
+
+#include "common/common_types.h"
+
+namespace Kernel {
+class HLERequestContext;
+class KReadableEvent;
+} // namespace Kernel
+
+namespace Service::android {
+
+enum class TransactionId {
+ RequestBuffer = 1,
+ SetBufferCount = 2,
+ DequeueBuffer = 3,
+ DetachBuffer = 4,
+ DetachNextBuffer = 5,
+ AttachBuffer = 6,
+ QueueBuffer = 7,
+ CancelBuffer = 8,
+ Query = 9,
+ Connect = 10,
+ Disconnect = 11,
+ AllocateBuffers = 13,
+ SetPreallocatedBuffer = 14,
+ GetBufferHistory = 17,
+};
+
+class IBinder {
+public:
+ virtual void Transact(Kernel::HLERequestContext& ctx, android::TransactionId code,
+ u32 flags) = 0;
+ virtual Kernel::KReadableEvent& GetNativeHandle() = 0;
+};
+
+} // namespace Service::android
diff --git a/src/core/hle/service/nvflinger/buffer_item.h b/src/core/hle/service/nvflinger/buffer_item.h
new file mode 100644
index 000000000..64b82b851
--- /dev/null
+++ b/src/core/hle/service/nvflinger/buffer_item.h
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Copyright 2021 yuzu Emulator Project
+// Copyright 2014 The Android Open Source Project
+// Parts of this implementation were base on:
+// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferItem.h
+
+#pragma once
+
+#include <memory>
+
+#include "common/common_types.h"
+#include "common/math_util.h"
+#include "core/hle/service/nvflinger/ui/fence.h"
+#include "core/hle/service/nvflinger/window.h"
+
+namespace Service::android {
+
+class GraphicBuffer;
+
+class BufferItem final {
+public:
+ constexpr BufferItem() = default;
+
+ std::shared_ptr<GraphicBuffer> graphic_buffer;
+ Fence fence;
+ Common::Rectangle<s32> crop;
+ NativeWindowTransform transform{};
+ u32 scaling_mode{};
+ s64 timestamp{};
+ bool is_auto_timestamp{};
+ u64 frame_number{};
+
+ // The default value for buf, used to indicate this doesn't correspond to a slot.
+ static constexpr s32 INVALID_BUFFER_SLOT = -1;
+ union {
+ s32 slot{INVALID_BUFFER_SLOT};
+ s32 buf;
+ };
+
+ bool is_droppable{};
+ bool acquire_called{};
+ bool transform_to_display_inverse{};
+ s32 swap_interval{};
+};
+
+} // namespace Service::android
diff --git a/src/core/hle/service/nvflinger/buffer_item_consumer.cpp b/src/core/hle/service/nvflinger/buffer_item_consumer.cpp
new file mode 100644
index 000000000..d7ee5362b
--- /dev/null
+++ b/src/core/hle/service/nvflinger/buffer_item_consumer.cpp
@@ -0,0 +1,59 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Copyright 2021 yuzu Emulator Project
+// Copyright 2012 The Android Open Source Project
+// Parts of this implementation were base on:
+// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/BufferItemConsumer.cpp
+
+#include "common/assert.h"
+#include "common/logging/log.h"
+#include "core/hle/service/nvflinger/buffer_item.h"
+#include "core/hle/service/nvflinger/buffer_item_consumer.h"
+#include "core/hle/service/nvflinger/buffer_queue_consumer.h"
+
+namespace Service::android {
+
+BufferItemConsumer::BufferItemConsumer(std::unique_ptr<BufferQueueConsumer> consumer_)
+ : ConsumerBase{std::move(consumer_)} {}
+
+Status BufferItemConsumer::AcquireBuffer(BufferItem* item, std::chrono::nanoseconds present_when,
+ bool wait_for_fence) {
+ if (!item) {
+ return Status::BadValue;
+ }
+
+ std::scoped_lock lock{mutex};
+
+ if (const auto status = AcquireBufferLocked(item, present_when); status != Status::NoError) {
+ if (status != Status::NoBufferAvailable) {
+ LOG_ERROR(Service_NVFlinger, "Failed to acquire buffer: {}", status);
+ }
+ return status;
+ }
+
+ if (wait_for_fence) {
+ UNIMPLEMENTED();
+ }
+
+ item->graphic_buffer = slots[item->slot].graphic_buffer;
+
+ return Status::NoError;
+}
+
+Status BufferItemConsumer::ReleaseBuffer(const BufferItem& item, Fence& release_fence) {
+ std::scoped_lock lock{mutex};
+
+ if (const auto status = AddReleaseFenceLocked(item.buf, item.graphic_buffer, release_fence);
+ status != Status::NoError) {
+ LOG_ERROR(Service_NVFlinger, "Failed to add fence: {}", status);
+ }
+
+ if (const auto status = ReleaseBufferLocked(item.buf, item.graphic_buffer);
+ status != Status::NoError) {
+ LOG_WARNING(Service_NVFlinger, "Failed to release buffer: {}", status);
+ return status;
+ }
+
+ return Status::NoError;
+}
+
+} // namespace Service::android
diff --git a/src/core/hle/service/nvflinger/buffer_item_consumer.h b/src/core/hle/service/nvflinger/buffer_item_consumer.h
new file mode 100644
index 000000000..536db81e2
--- /dev/null
+++ b/src/core/hle/service/nvflinger/buffer_item_consumer.h
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Copyright 2021 yuzu Emulator Project
+// Copyright 2012 The Android Open Source Project
+// Parts of this implementation were base on:
+// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferItemConsumer.h
+
+#pragma once
+
+#include <chrono>
+#include <memory>
+
+#include "common/common_types.h"
+#include "core/hle/service/nvflinger/consumer_base.h"
+#include "core/hle/service/nvflinger/status.h"
+
+namespace Service::android {
+
+class BufferItem;
+
+class BufferItemConsumer final : public ConsumerBase {
+public:
+ explicit BufferItemConsumer(std::unique_ptr<BufferQueueConsumer> consumer);
+ Status AcquireBuffer(BufferItem* item, std::chrono::nanoseconds present_when,
+ bool wait_for_fence = true);
+ Status ReleaseBuffer(const BufferItem& item, Fence& release_fence);
+};
+
+} // namespace Service::android
diff --git a/src/core/hle/service/nvflinger/buffer_queue.cpp b/src/core/hle/service/nvflinger/buffer_queue.cpp
deleted file mode 100644
index 5fead6d1b..000000000
--- a/src/core/hle/service/nvflinger/buffer_queue.cpp
+++ /dev/null
@@ -1,206 +0,0 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <algorithm>
-
-#include "common/assert.h"
-#include "common/logging/log.h"
-#include "core/core.h"
-#include "core/hle/kernel/k_writable_event.h"
-#include "core/hle/kernel/kernel.h"
-#include "core/hle/service/kernel_helpers.h"
-#include "core/hle/service/nvflinger/buffer_queue.h"
-
-namespace Service::NVFlinger {
-
-BufferQueue::BufferQueue(Kernel::KernelCore& kernel, u32 id_, u64 layer_id_,
- KernelHelpers::ServiceContext& service_context_)
- : id(id_), layer_id(layer_id_), service_context{service_context_} {
- buffer_wait_event = service_context.CreateEvent("BufferQueue:WaitEvent");
-}
-
-BufferQueue::~BufferQueue() {
- service_context.CloseEvent(buffer_wait_event);
-}
-
-void BufferQueue::SetPreallocatedBuffer(u32 slot, const IGBPBuffer& igbp_buffer) {
- ASSERT(slot < buffer_slots);
- LOG_WARNING(Service, "Adding graphics buffer {}", slot);
-
- {
- std::unique_lock lock{free_buffers_mutex};
- free_buffers.push_back(slot);
- }
- free_buffers_condition.notify_one();
-
- buffers[slot] = {
- .slot = slot,
- .status = Buffer::Status::Free,
- .igbp_buffer = igbp_buffer,
- .transform = {},
- .crop_rect = {},
- .swap_interval = 0,
- .multi_fence = {},
- };
-
- buffer_wait_event->GetWritableEvent().Signal();
-}
-
-std::optional<std::pair<u32, Service::Nvidia::MultiFence*>> BufferQueue::DequeueBuffer(u32 width,
- u32 height) {
- // Wait for first request before trying to dequeue
- {
- std::unique_lock lock{free_buffers_mutex};
- free_buffers_condition.wait(lock, [this] { return !free_buffers.empty() || !is_connect; });
- }
-
- if (!is_connect) {
- // Buffer was disconnected while the thread was blocked, this is most likely due to
- // emulation being stopped
- return std::nullopt;
- }
-
- std::unique_lock lock{free_buffers_mutex};
-
- auto f_itr = free_buffers.begin();
- auto slot = buffers.size();
-
- while (f_itr != free_buffers.end()) {
- const Buffer& buffer = buffers[*f_itr];
- if (buffer.status == Buffer::Status::Free && buffer.igbp_buffer.width == width &&
- buffer.igbp_buffer.height == height) {
- slot = *f_itr;
- free_buffers.erase(f_itr);
- break;
- }
- ++f_itr;
- }
- if (slot == buffers.size()) {
- return std::nullopt;
- }
- buffers[slot].status = Buffer::Status::Dequeued;
- return {{buffers[slot].slot, &buffers[slot].multi_fence}};
-}
-
-const IGBPBuffer& BufferQueue::RequestBuffer(u32 slot) const {
- ASSERT(slot < buffers.size());
- ASSERT(buffers[slot].status == Buffer::Status::Dequeued);
- ASSERT(buffers[slot].slot == slot);
-
- return buffers[slot].igbp_buffer;
-}
-
-void BufferQueue::QueueBuffer(u32 slot, BufferTransformFlags transform,
- const Common::Rectangle<int>& crop_rect, u32 swap_interval,
- Service::Nvidia::MultiFence& multi_fence) {
- ASSERT(slot < buffers.size());
- ASSERT(buffers[slot].status == Buffer::Status::Dequeued);
- ASSERT(buffers[slot].slot == slot);
-
- buffers[slot].status = Buffer::Status::Queued;
- buffers[slot].transform = transform;
- buffers[slot].crop_rect = crop_rect;
- buffers[slot].swap_interval = swap_interval;
- buffers[slot].multi_fence = multi_fence;
- std::unique_lock lock{queue_sequence_mutex};
- queue_sequence.push_back(slot);
-}
-
-void BufferQueue::CancelBuffer(u32 slot, const Service::Nvidia::MultiFence& multi_fence) {
- ASSERT(slot < buffers.size());
- ASSERT(buffers[slot].status != Buffer::Status::Free);
- ASSERT(buffers[slot].slot == slot);
-
- buffers[slot].status = Buffer::Status::Free;
- buffers[slot].multi_fence = multi_fence;
- buffers[slot].swap_interval = 0;
-
- {
- std::unique_lock lock{free_buffers_mutex};
- free_buffers.push_back(slot);
- }
- free_buffers_condition.notify_one();
-
- buffer_wait_event->GetWritableEvent().Signal();
-}
-
-std::optional<std::reference_wrapper<const BufferQueue::Buffer>> BufferQueue::AcquireBuffer() {
- std::unique_lock lock{queue_sequence_mutex};
- std::size_t buffer_slot = buffers.size();
- // Iterate to find a queued buffer matching the requested slot.
- while (buffer_slot == buffers.size() && !queue_sequence.empty()) {
- const auto slot = static_cast<std::size_t>(queue_sequence.front());
- ASSERT(slot < buffers.size());
- if (buffers[slot].status == Buffer::Status::Queued) {
- ASSERT(buffers[slot].slot == slot);
- buffer_slot = slot;
- }
- queue_sequence.pop_front();
- }
- if (buffer_slot == buffers.size()) {
- return std::nullopt;
- }
- buffers[buffer_slot].status = Buffer::Status::Acquired;
- return {{buffers[buffer_slot]}};
-}
-
-void BufferQueue::ReleaseBuffer(u32 slot) {
- ASSERT(slot < buffers.size());
- ASSERT(buffers[slot].status == Buffer::Status::Acquired);
- ASSERT(buffers[slot].slot == slot);
-
- buffers[slot].status = Buffer::Status::Free;
- {
- std::unique_lock lock{free_buffers_mutex};
- free_buffers.push_back(slot);
- }
- free_buffers_condition.notify_one();
-
- buffer_wait_event->GetWritableEvent().Signal();
-}
-
-void BufferQueue::Connect() {
- std::unique_lock lock{queue_sequence_mutex};
- queue_sequence.clear();
- is_connect = true;
-}
-
-void BufferQueue::Disconnect() {
- buffers.fill({});
- {
- std::unique_lock lock{queue_sequence_mutex};
- queue_sequence.clear();
- }
- buffer_wait_event->GetWritableEvent().Signal();
- is_connect = false;
- free_buffers_condition.notify_one();
-}
-
-u32 BufferQueue::Query(QueryType type) {
- LOG_WARNING(Service, "(STUBBED) called type={}", type);
-
- switch (type) {
- case QueryType::NativeWindowFormat:
- return static_cast<u32>(PixelFormat::RGBA8888);
- case QueryType::NativeWindowWidth:
- case QueryType::NativeWindowHeight:
- break;
- case QueryType::NativeWindowMinUndequeuedBuffers:
- return 0;
- case QueryType::NativeWindowConsumerUsageBits:
- return 0;
- }
- UNIMPLEMENTED_MSG("Unimplemented query type={}", type);
- return 0;
-}
-
-Kernel::KWritableEvent& BufferQueue::GetWritableBufferWaitEvent() {
- return buffer_wait_event->GetWritableEvent();
-}
-
-Kernel::KReadableEvent& BufferQueue::GetBufferWaitEvent() {
- return buffer_wait_event->GetReadableEvent();
-}
-
-} // namespace Service::NVFlinger
diff --git a/src/core/hle/service/nvflinger/buffer_queue.h b/src/core/hle/service/nvflinger/buffer_queue.h
deleted file mode 100644
index f2a579133..000000000
--- a/src/core/hle/service/nvflinger/buffer_queue.h
+++ /dev/null
@@ -1,154 +0,0 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <condition_variable>
-#include <list>
-#include <mutex>
-#include <optional>
-
-#include "common/common_funcs.h"
-#include "common/math_util.h"
-#include "common/swap.h"
-#include "core/hle/kernel/k_event.h"
-#include "core/hle/kernel/k_readable_event.h"
-#include "core/hle/service/nvdrv/nvdata.h"
-
-namespace Kernel {
-class KernelCore;
-class KEvent;
-class KReadableEvent;
-class KWritableEvent;
-} // namespace Kernel
-
-namespace Service::KernelHelpers {
-class ServiceContext;
-} // namespace Service::KernelHelpers
-
-namespace Service::NVFlinger {
-
-constexpr u32 buffer_slots = 0x40;
-struct IGBPBuffer {
- u32_le magic;
- u32_le width;
- u32_le height;
- u32_le stride;
- u32_le format;
- u32_le usage;
- INSERT_PADDING_WORDS(1);
- u32_le index;
- INSERT_PADDING_WORDS(3);
- u32_le gpu_buffer_id;
- INSERT_PADDING_WORDS(6);
- u32_le external_format;
- INSERT_PADDING_WORDS(10);
- u32_le nvmap_handle;
- u32_le offset;
- INSERT_PADDING_WORDS(60);
-};
-
-static_assert(sizeof(IGBPBuffer) == 0x16C, "IGBPBuffer has wrong size");
-
-class BufferQueue final {
-public:
- enum class QueryType {
- NativeWindowWidth = 0,
- NativeWindowHeight = 1,
- NativeWindowFormat = 2,
- /// The minimum number of buffers that must remain un-dequeued after a buffer has been
- /// queued
- NativeWindowMinUndequeuedBuffers = 3,
- /// The consumer gralloc usage bits currently set by the consumer
- NativeWindowConsumerUsageBits = 10,
- };
-
- explicit BufferQueue(Kernel::KernelCore& kernel, u32 id_, u64 layer_id_,
- KernelHelpers::ServiceContext& service_context_);
- ~BufferQueue();
-
- 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)
- FlipV = 0x02,
- /// Rotate source image 90 degrees clockwise
- Rotate90 = 0x04,
- /// Rotate source image 180 degrees
- Rotate180 = 0x03,
- /// Rotate source image 270 degrees clockwise
- Rotate270 = 0x07,
- };
-
- enum class PixelFormat : u32 {
- RGBA8888 = 1,
- RGBX8888 = 2,
- RGB888 = 3,
- RGB565 = 4,
- BGRA8888 = 5,
- RGBA5551 = 6,
- RRGBA4444 = 7,
- };
-
- struct Buffer {
- enum class Status { Free = 0, Queued = 1, Dequeued = 2, Acquired = 3 };
-
- u32 slot;
- Status status = Status::Free;
- IGBPBuffer igbp_buffer;
- BufferTransformFlags transform;
- Common::Rectangle<int> crop_rect;
- u32 swap_interval;
- Service::Nvidia::MultiFence multi_fence;
- };
-
- void SetPreallocatedBuffer(u32 slot, const IGBPBuffer& igbp_buffer);
- std::optional<std::pair<u32, Service::Nvidia::MultiFence*>> DequeueBuffer(u32 width,
- u32 height);
- const IGBPBuffer& RequestBuffer(u32 slot) const;
- void QueueBuffer(u32 slot, BufferTransformFlags transform,
- const Common::Rectangle<int>& crop_rect, u32 swap_interval,
- Service::Nvidia::MultiFence& multi_fence);
- void CancelBuffer(u32 slot, const Service::Nvidia::MultiFence& multi_fence);
- std::optional<std::reference_wrapper<const Buffer>> AcquireBuffer();
- void ReleaseBuffer(u32 slot);
- void Connect();
- void Disconnect();
- u32 Query(QueryType type);
-
- u32 GetId() const {
- return id;
- }
-
- bool IsConnected() const {
- return is_connect;
- }
-
- Kernel::KWritableEvent& GetWritableBufferWaitEvent();
-
- Kernel::KReadableEvent& GetBufferWaitEvent();
-
-private:
- BufferQueue(const BufferQueue&) = delete;
-
- u32 id{};
- u64 layer_id{};
- std::atomic_bool is_connect{};
-
- std::list<u32> free_buffers;
- std::array<Buffer, buffer_slots> buffers;
- std::list<u32> queue_sequence;
- Kernel::KEvent* buffer_wait_event{};
-
- std::mutex free_buffers_mutex;
- std::condition_variable free_buffers_condition;
-
- std::mutex queue_sequence_mutex;
-
- KernelHelpers::ServiceContext& service_context;
-};
-
-} // namespace Service::NVFlinger
diff --git a/src/core/hle/service/nvflinger/buffer_queue_consumer.cpp b/src/core/hle/service/nvflinger/buffer_queue_consumer.cpp
new file mode 100644
index 000000000..3ab9a8c05
--- /dev/null
+++ b/src/core/hle/service/nvflinger/buffer_queue_consumer.cpp
@@ -0,0 +1,231 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Copyright 2021 yuzu Emulator Project
+// Copyright 2014 The Android Open Source Project
+// Parts of this implementation were base on:
+// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/BufferQueueConsumer.cpp
+
+#include "common/logging/log.h"
+#include "core/hle/service/nvflinger/buffer_item.h"
+#include "core/hle/service/nvflinger/buffer_queue_consumer.h"
+#include "core/hle/service/nvflinger/buffer_queue_core.h"
+#include "core/hle/service/nvflinger/producer_listener.h"
+
+namespace Service::android {
+
+BufferQueueConsumer::BufferQueueConsumer(std::shared_ptr<BufferQueueCore> core_)
+ : core{std::move(core_)}, slots{core->slots} {}
+
+BufferQueueConsumer::~BufferQueueConsumer() = default;
+
+Status BufferQueueConsumer::AcquireBuffer(BufferItem* out_buffer,
+ std::chrono::nanoseconds expected_present) {
+ std::scoped_lock lock{core->mutex};
+
+ // Check that the consumer doesn't currently have the maximum number of buffers acquired.
+ const s32 num_acquired_buffers{
+ static_cast<s32>(std::count_if(slots.begin(), slots.end(), [](const auto& slot) {
+ return slot.buffer_state == BufferState::Acquired;
+ }))};
+
+ if (num_acquired_buffers >= core->max_acquired_buffer_count + 1) {
+ LOG_ERROR(Service_NVFlinger, "max acquired buffer count reached: {} (max {})",
+ num_acquired_buffers, core->max_acquired_buffer_count);
+ return Status::InvalidOperation;
+ }
+
+ // Check if the queue is empty.
+ if (core->queue.empty()) {
+ return Status::NoBufferAvailable;
+ }
+
+ auto front(core->queue.begin());
+
+ // If expected_present is specified, we may not want to return a buffer yet.
+ if (expected_present.count() != 0) {
+ constexpr auto MAX_REASONABLE_NSEC = 1000000000LL; // 1 second
+
+ // The expected_present argument indicates when the buffer is expected to be presented
+ // on-screen.
+ while (core->queue.size() > 1 && !core->queue[0].is_auto_timestamp) {
+ const auto& buffer_item{core->queue[1]};
+
+ // If entry[1] is timely, drop entry[0] (and repeat).
+ const auto desired_present = buffer_item.timestamp;
+ if (desired_present < expected_present.count() - MAX_REASONABLE_NSEC ||
+ desired_present > expected_present.count()) {
+ // This buffer is set to display in the near future, or desired_present is garbage.
+ LOG_DEBUG(Service_NVFlinger, "nodrop desire={} expect={}", desired_present,
+ expected_present.count());
+ break;
+ }
+
+ LOG_DEBUG(Service_NVFlinger, "drop desire={} expect={} size={}", desired_present,
+ expected_present.count(), core->queue.size());
+
+ if (core->StillTracking(*front)) {
+ // Front buffer is still in mSlots, so mark the slot as free
+ slots[front->slot].buffer_state = BufferState::Free;
+ }
+
+ core->queue.erase(front);
+ front = core->queue.begin();
+ }
+
+ // See if the front buffer is ready to be acquired.
+ const auto desired_present = front->timestamp;
+ if (desired_present > expected_present.count() &&
+ desired_present < expected_present.count() + MAX_REASONABLE_NSEC) {
+ LOG_DEBUG(Service_NVFlinger, "defer desire={} expect={}", desired_present,
+ expected_present.count());
+ return Status::PresentLater;
+ }
+
+ LOG_DEBUG(Service_NVFlinger, "accept desire={} expect={}", desired_present,
+ expected_present.count());
+ }
+
+ const auto slot = front->slot;
+ *out_buffer = *front;
+
+ LOG_DEBUG(Service_NVFlinger, "acquiring slot={}", slot);
+
+ // If the front buffer is still being tracked, update its slot state
+ if (core->StillTracking(*front)) {
+ slots[slot].acquire_called = true;
+ slots[slot].needs_cleanup_on_release = false;
+ slots[slot].buffer_state = BufferState::Acquired;
+ slots[slot].fence = Fence::NoFence();
+ }
+
+ // If the buffer has previously been acquired by the consumer, set graphic_buffer to nullptr to
+ // avoid unnecessarily remapping this buffer on the consumer side.
+ if (out_buffer->acquire_called) {
+ out_buffer->graphic_buffer = nullptr;
+ }
+
+ core->queue.erase(front);
+
+ // We might have freed a slot while dropping old buffers, or the producer may be blocked
+ // waiting for the number of buffers in the queue to decrease.
+ core->SignalDequeueCondition();
+
+ return Status::NoError;
+}
+
+Status BufferQueueConsumer::ReleaseBuffer(s32 slot, u64 frame_number, const Fence& release_fence) {
+ if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
+ LOG_ERROR(Service_NVFlinger, "slot {} out of range", slot);
+ return Status::BadValue;
+ }
+
+ std::shared_ptr<IProducerListener> listener;
+ {
+ std::scoped_lock lock{core->mutex};
+
+ // If the frame number has changed because the buffer has been reallocated, we can ignore
+ // this ReleaseBuffer for the old buffer.
+ if (frame_number != slots[slot].frame_number) {
+ return Status::StaleBufferSlot;
+ }
+
+ // Make sure this buffer hasn't been queued while acquired by the consumer.
+ auto current(core->queue.begin());
+ while (current != core->queue.end()) {
+ if (current->slot == slot) {
+ LOG_ERROR(Service_NVFlinger, "buffer slot {} pending release is currently queued",
+ slot);
+ return Status::BadValue;
+ }
+ ++current;
+ }
+
+ if (slots[slot].buffer_state == BufferState::Acquired) {
+ slots[slot].fence = release_fence;
+ slots[slot].buffer_state = BufferState::Free;
+
+ listener = core->connected_producer_listener;
+
+ LOG_DEBUG(Service_NVFlinger, "releasing slot {}", slot);
+ } else if (slots[slot].needs_cleanup_on_release) {
+ LOG_DEBUG(Service_NVFlinger, "releasing a stale buffer slot {} (state = {})", slot,
+ slots[slot].buffer_state);
+
+ slots[slot].needs_cleanup_on_release = false;
+
+ return Status::StaleBufferSlot;
+ } else {
+ LOG_ERROR(Service_NVFlinger, "attempted to release buffer slot {} but its state was {}",
+ slot, slots[slot].buffer_state);
+
+ return Status::BadValue;
+ }
+
+ core->SignalDequeueCondition();
+ }
+
+ // Call back without lock held
+ if (listener != nullptr) {
+ listener->OnBufferReleased();
+ }
+
+ return Status::NoError;
+}
+
+Status BufferQueueConsumer::Connect(std::shared_ptr<IConsumerListener> consumer_listener,
+ bool controlled_by_app) {
+ if (consumer_listener == nullptr) {
+ LOG_ERROR(Service_NVFlinger, "consumer_listener may not be nullptr");
+ return Status::BadValue;
+ }
+
+ LOG_DEBUG(Service_NVFlinger, "controlled_by_app={}", controlled_by_app);
+
+ std::scoped_lock lock{core->mutex};
+
+ if (core->is_abandoned) {
+ LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned");
+ return Status::NoInit;
+ }
+
+ core->consumer_listener = consumer_listener;
+ core->consumer_controlled_by_app = controlled_by_app;
+
+ return Status::NoError;
+}
+
+Status BufferQueueConsumer::GetReleasedBuffers(u64* out_slot_mask) {
+ if (out_slot_mask == nullptr) {
+ LOG_ERROR(Service_NVFlinger, "out_slot_mask may not be nullptr");
+ return Status::BadValue;
+ }
+
+ std::scoped_lock lock{core->mutex};
+
+ if (core->is_abandoned) {
+ LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned");
+ return Status::NoInit;
+ }
+
+ u64 mask = 0;
+ for (int s = 0; s < BufferQueueDefs::NUM_BUFFER_SLOTS; ++s) {
+ if (!slots[s].acquire_called) {
+ mask |= (1ULL << s);
+ }
+ }
+
+ // Remove from the mask queued buffers for which acquire has been called, since the consumer
+ // will not receive their buffer addresses and so must retain their cached information
+ auto current(core->queue.begin());
+ while (current != core->queue.end()) {
+ if (current->acquire_called) {
+ mask &= ~(1ULL << current->slot);
+ }
+ ++current;
+ }
+
+ LOG_DEBUG(Service_NVFlinger, "returning mask {}", mask);
+ *out_slot_mask = mask;
+ return Status::NoError;
+}
+
+} // namespace Service::android
diff --git a/src/core/hle/service/nvflinger/buffer_queue_consumer.h b/src/core/hle/service/nvflinger/buffer_queue_consumer.h
new file mode 100644
index 000000000..8a047fe06
--- /dev/null
+++ b/src/core/hle/service/nvflinger/buffer_queue_consumer.h
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Copyright 2021 yuzu Emulator Project
+// Copyright 2014 The Android Open Source Project
+// Parts of this implementation were base on:
+// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferQueueConsumer.h
+
+#pragma once
+
+#include <chrono>
+#include <memory>
+
+#include "common/common_types.h"
+#include "core/hle/service/nvflinger/buffer_queue_defs.h"
+#include "core/hle/service/nvflinger/status.h"
+
+namespace Service::android {
+
+class BufferItem;
+class BufferQueueCore;
+class IConsumerListener;
+
+class BufferQueueConsumer final {
+public:
+ explicit BufferQueueConsumer(std::shared_ptr<BufferQueueCore> core_);
+ ~BufferQueueConsumer();
+
+ Status AcquireBuffer(BufferItem* out_buffer, std::chrono::nanoseconds expected_present);
+ Status ReleaseBuffer(s32 slot, u64 frame_number, const Fence& release_fence);
+ Status Connect(std::shared_ptr<IConsumerListener> consumer_listener, bool controlled_by_app);
+ Status GetReleasedBuffers(u64* out_slot_mask);
+
+private:
+ std::shared_ptr<BufferQueueCore> core;
+ BufferQueueDefs::SlotsType& slots;
+};
+
+} // namespace Service::android
diff --git a/src/core/hle/service/nvflinger/buffer_queue_core.cpp b/src/core/hle/service/nvflinger/buffer_queue_core.cpp
new file mode 100644
index 000000000..ec5aabaeb
--- /dev/null
+++ b/src/core/hle/service/nvflinger/buffer_queue_core.cpp
@@ -0,0 +1,117 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Copyright 2021 yuzu Emulator Project
+// Copyright 2014 The Android Open Source Project
+// Parts of this implementation were base on:
+// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/BufferQueueCore.cpp
+
+#include "common/assert.h"
+
+#include "core/hle/service/nvflinger/buffer_queue_core.h"
+
+namespace Service::android {
+
+BufferQueueCore::BufferQueueCore() = default;
+
+BufferQueueCore::~BufferQueueCore() = default;
+
+void BufferQueueCore::NotifyShutdown() {
+ std::scoped_lock lock{mutex};
+
+ is_shutting_down = true;
+
+ SignalDequeueCondition();
+}
+
+void BufferQueueCore::SignalDequeueCondition() {
+ dequeue_condition.notify_all();
+}
+
+bool BufferQueueCore::WaitForDequeueCondition() {
+ if (is_shutting_down) {
+ return false;
+ }
+
+ dequeue_condition.wait(mutex);
+
+ return true;
+}
+
+s32 BufferQueueCore::GetMinUndequeuedBufferCountLocked(bool async) const {
+ // If DequeueBuffer is allowed to error out, we don't have to add an extra buffer.
+ if (!use_async_buffer) {
+ return max_acquired_buffer_count;
+ }
+
+ if (dequeue_buffer_cannot_block || async) {
+ return max_acquired_buffer_count + 1;
+ }
+
+ return max_acquired_buffer_count;
+}
+
+s32 BufferQueueCore::GetMinMaxBufferCountLocked(bool async) const {
+ return GetMinUndequeuedBufferCountLocked(async) + 1;
+}
+
+s32 BufferQueueCore::GetMaxBufferCountLocked(bool async) const {
+ const auto min_buffer_count = GetMinMaxBufferCountLocked(async);
+ auto max_buffer_count = std::max(default_max_buffer_count, min_buffer_count);
+
+ if (override_max_buffer_count != 0) {
+ ASSERT(override_max_buffer_count >= min_buffer_count);
+ max_buffer_count = override_max_buffer_count;
+ }
+
+ // Any buffers that are dequeued by the producer or sitting in the queue waiting to be consumed
+ // need to have their slots preserved.
+ for (s32 slot = max_buffer_count; slot < BufferQueueDefs::NUM_BUFFER_SLOTS; ++slot) {
+ const auto state = slots[slot].buffer_state;
+ if (state == BufferState::Queued || state == BufferState::Dequeued) {
+ max_buffer_count = slot + 1;
+ }
+ }
+
+ return max_buffer_count;
+}
+
+s32 BufferQueueCore::GetPreallocatedBufferCountLocked() const {
+ return static_cast<s32>(std::count_if(slots.begin(), slots.end(),
+ [](const auto& slot) { return slot.is_preallocated; }));
+}
+
+void BufferQueueCore::FreeBufferLocked(s32 slot) {
+ LOG_DEBUG(Service_NVFlinger, "slot {}", slot);
+
+ slots[slot].graphic_buffer.reset();
+
+ if (slots[slot].buffer_state == BufferState::Acquired) {
+ slots[slot].needs_cleanup_on_release = true;
+ }
+
+ slots[slot].buffer_state = BufferState::Free;
+ slots[slot].frame_number = UINT32_MAX;
+ slots[slot].acquire_called = false;
+ slots[slot].fence = Fence::NoFence();
+}
+
+void BufferQueueCore::FreeAllBuffersLocked() {
+ buffer_has_been_queued = false;
+
+ for (s32 slot = 0; slot < BufferQueueDefs::NUM_BUFFER_SLOTS; ++slot) {
+ FreeBufferLocked(slot);
+ }
+}
+
+bool BufferQueueCore::StillTracking(const BufferItem& item) const {
+ const BufferSlot& slot = slots[item.slot];
+
+ return (slot.graphic_buffer != nullptr) && (item.graphic_buffer == slot.graphic_buffer);
+}
+
+void BufferQueueCore::WaitWhileAllocatingLocked() const {
+ while (is_allocating) {
+ is_allocating_condition.wait(mutex);
+ }
+}
+
+} // namespace Service::android
diff --git a/src/core/hle/service/nvflinger/buffer_queue_core.h b/src/core/hle/service/nvflinger/buffer_queue_core.h
new file mode 100644
index 000000000..e4e0937cb
--- /dev/null
+++ b/src/core/hle/service/nvflinger/buffer_queue_core.h
@@ -0,0 +1,79 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Copyright 2021 yuzu Emulator Project
+// Copyright 2014 The Android Open Source Project
+// Parts of this implementation were base on:
+// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferQueueCore.h
+
+#pragma once
+
+#include <condition_variable>
+#include <list>
+#include <memory>
+#include <mutex>
+#include <set>
+#include <vector>
+
+#include "core/hle/service/nvflinger/buffer_item.h"
+#include "core/hle/service/nvflinger/buffer_queue_defs.h"
+#include "core/hle/service/nvflinger/pixel_format.h"
+#include "core/hle/service/nvflinger/status.h"
+#include "core/hle/service/nvflinger/window.h"
+
+namespace Service::android {
+
+class IConsumerListener;
+class IProducerListener;
+
+class BufferQueueCore final {
+ friend class BufferQueueProducer;
+ friend class BufferQueueConsumer;
+
+public:
+ static constexpr s32 INVALID_BUFFER_SLOT = BufferItem::INVALID_BUFFER_SLOT;
+
+ BufferQueueCore();
+ ~BufferQueueCore();
+
+ void NotifyShutdown();
+
+private:
+ void SignalDequeueCondition();
+ bool WaitForDequeueCondition();
+
+ s32 GetMinUndequeuedBufferCountLocked(bool async) const;
+ s32 GetMinMaxBufferCountLocked(bool async) const;
+ s32 GetMaxBufferCountLocked(bool async) const;
+ s32 GetPreallocatedBufferCountLocked() const;
+ void FreeBufferLocked(s32 slot);
+ void FreeAllBuffersLocked();
+ bool StillTracking(const BufferItem& item) const;
+ void WaitWhileAllocatingLocked() const;
+
+private:
+ mutable std::mutex mutex;
+ bool is_abandoned{};
+ bool consumer_controlled_by_app{};
+ std::shared_ptr<IConsumerListener> consumer_listener;
+ u32 consumer_usage_bit{};
+ NativeWindowApi connected_api{NativeWindowApi::NoConnectedApi};
+ std::shared_ptr<IProducerListener> connected_producer_listener;
+ BufferQueueDefs::SlotsType slots{};
+ std::vector<BufferItem> queue;
+ s32 override_max_buffer_count{};
+ mutable std::condition_variable_any dequeue_condition;
+ const bool use_async_buffer{}; // This is always disabled on HOS
+ bool dequeue_buffer_cannot_block{};
+ PixelFormat default_buffer_format{PixelFormat::Rgba8888};
+ u32 default_width{1};
+ u32 default_height{1};
+ s32 default_max_buffer_count{2};
+ const s32 max_acquired_buffer_count{}; // This is always zero on HOS
+ bool buffer_has_been_queued{};
+ u64 frame_counter{};
+ u32 transform_hint{};
+ bool is_allocating{};
+ mutable std::condition_variable_any is_allocating_condition;
+ bool is_shutting_down{};
+};
+
+} // namespace Service::android
diff --git a/src/core/hle/service/nvflinger/buffer_queue_defs.h b/src/core/hle/service/nvflinger/buffer_queue_defs.h
new file mode 100644
index 000000000..387d3d36a
--- /dev/null
+++ b/src/core/hle/service/nvflinger/buffer_queue_defs.h
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Copyright 2021 yuzu Emulator Project
+// Copyright 2014 The Android Open Source Project
+// Parts of this implementation were base on:
+// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferQueueDefs.h
+
+#pragma once
+
+#include <array>
+
+#include "common/common_types.h"
+#include "core/hle/service/nvflinger/buffer_slot.h"
+
+namespace Service::android::BufferQueueDefs {
+
+// BufferQueue will keep track of at most this value of buffers.
+constexpr s32 NUM_BUFFER_SLOTS = 64;
+
+using SlotsType = std::array<BufferSlot, NUM_BUFFER_SLOTS>;
+
+} // namespace Service::android::BufferQueueDefs
diff --git a/src/core/hle/service/nvflinger/buffer_queue_producer.cpp b/src/core/hle/service/nvflinger/buffer_queue_producer.cpp
new file mode 100644
index 000000000..6f604a88e
--- /dev/null
+++ b/src/core/hle/service/nvflinger/buffer_queue_producer.cpp
@@ -0,0 +1,923 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Copyright 2021 yuzu Emulator Project
+// Copyright 2014 The Android Open Source Project
+// Parts of this implementation were base on:
+// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/BufferQueueProducer.cpp
+
+#include "common/assert.h"
+#include "common/logging/log.h"
+#include "common/settings.h"
+#include "core/core.h"
+#include "core/hle/kernel/hle_ipc.h"
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/kernel/k_readable_event.h"
+#include "core/hle/kernel/k_writable_event.h"
+#include "core/hle/kernel/kernel.h"
+#include "core/hle/service/kernel_helpers.h"
+#include "core/hle/service/nvdrv/nvdrv.h"
+#include "core/hle/service/nvflinger/buffer_queue_core.h"
+#include "core/hle/service/nvflinger/buffer_queue_producer.h"
+#include "core/hle/service/nvflinger/consumer_listener.h"
+#include "core/hle/service/nvflinger/parcel.h"
+#include "core/hle/service/nvflinger/ui/graphic_buffer.h"
+#include "core/hle/service/nvflinger/window.h"
+#include "core/hle/service/vi/vi.h"
+
+namespace Service::android {
+
+BufferQueueProducer::BufferQueueProducer(Service::KernelHelpers::ServiceContext& service_context_,
+ std::shared_ptr<BufferQueueCore> buffer_queue_core_)
+ : service_context{service_context_}, core{std::move(buffer_queue_core_)}, slots(core->slots) {
+ buffer_wait_event = service_context.CreateEvent("BufferQueue:WaitEvent");
+}
+
+BufferQueueProducer::~BufferQueueProducer() {
+ service_context.CloseEvent(buffer_wait_event);
+}
+
+Status BufferQueueProducer::RequestBuffer(s32 slot, std::shared_ptr<GraphicBuffer>* buf) {
+ LOG_DEBUG(Service_NVFlinger, "slot {}", slot);
+
+ std::scoped_lock lock{core->mutex};
+
+ if (core->is_abandoned) {
+ LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned");
+ return Status::NoInit;
+ }
+ if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
+ LOG_ERROR(Service_NVFlinger, "slot index {} out of range [0, {})", slot,
+ BufferQueueDefs::NUM_BUFFER_SLOTS);
+ return Status::BadValue;
+ } else if (slots[slot].buffer_state != BufferState::Dequeued) {
+ LOG_ERROR(Service_NVFlinger, "slot {} is not owned by the producer (state = {})", slot,
+ slots[slot].buffer_state);
+ return Status::BadValue;
+ }
+
+ slots[slot].request_buffer_called = true;
+ *buf = slots[slot].graphic_buffer;
+
+ return Status::NoError;
+}
+
+Status BufferQueueProducer::SetBufferCount(s32 buffer_count) {
+ LOG_DEBUG(Service_NVFlinger, "count = {}", buffer_count);
+
+ std::shared_ptr<IConsumerListener> listener;
+ {
+ std::scoped_lock lock{core->mutex};
+ core->WaitWhileAllocatingLocked();
+
+ if (core->is_abandoned) {
+ LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned");
+ return Status::NoInit;
+ }
+
+ if (buffer_count > BufferQueueDefs::NUM_BUFFER_SLOTS) {
+ LOG_ERROR(Service_NVFlinger, "buffer_count {} too large (max {})", buffer_count,
+ BufferQueueDefs::NUM_BUFFER_SLOTS);
+ return Status::BadValue;
+ }
+
+ // There must be no dequeued buffers when changing the buffer count.
+ for (s32 s{}; s < BufferQueueDefs::NUM_BUFFER_SLOTS; ++s) {
+ if (slots[s].buffer_state == BufferState::Dequeued) {
+ LOG_ERROR(Service_NVFlinger, "buffer owned by producer");
+ return Status::BadValue;
+ }
+ }
+
+ if (buffer_count == 0) {
+ core->override_max_buffer_count = 0;
+ core->SignalDequeueCondition();
+ return Status::NoError;
+ }
+
+ const s32 min_buffer_slots = core->GetMinMaxBufferCountLocked(false);
+ if (buffer_count < min_buffer_slots) {
+ LOG_ERROR(Service_NVFlinger, "requested buffer count {} is less than minimum {}",
+ buffer_count, min_buffer_slots);
+ return Status::BadValue;
+ }
+
+ // Here we are guaranteed that the producer doesn't have any dequeued buffers and will
+ // release all of its buffer references.
+ if (core->GetPreallocatedBufferCountLocked() <= 0) {
+ core->FreeAllBuffersLocked();
+ }
+
+ core->override_max_buffer_count = buffer_count;
+ core->SignalDequeueCondition();
+ buffer_wait_event->GetWritableEvent().Signal();
+ listener = core->consumer_listener;
+ }
+
+ // Call back without lock held
+ if (listener != nullptr) {
+ listener->OnBuffersReleased();
+ }
+
+ return Status::NoError;
+}
+
+Status BufferQueueProducer::WaitForFreeSlotThenRelock(bool async, s32* found,
+ Status* return_flags) const {
+ bool try_again = true;
+
+ while (try_again) {
+ if (core->is_abandoned) {
+ LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned");
+ return Status::NoInit;
+ }
+
+ const s32 max_buffer_count = core->GetMaxBufferCountLocked(async);
+ if (async && core->override_max_buffer_count) {
+ if (core->override_max_buffer_count < max_buffer_count) {
+ LOG_ERROR(Service_NVFlinger, "async mode is invalid with buffer count override");
+ return Status::BadValue;
+ }
+ }
+
+ // Free up any buffers that are in slots beyond the max buffer count
+ for (s32 s = max_buffer_count; s < BufferQueueDefs::NUM_BUFFER_SLOTS; ++s) {
+ ASSERT(slots[s].buffer_state == BufferState::Free);
+ if (slots[s].graphic_buffer != nullptr) {
+ core->FreeBufferLocked(s);
+ *return_flags |= Status::ReleaseAllBuffers;
+ }
+ }
+
+ // Look for a free buffer to give to the client
+ *found = BufferQueueCore::INVALID_BUFFER_SLOT;
+ s32 dequeued_count{};
+ s32 acquired_count{};
+ for (s32 s{}; s < max_buffer_count; ++s) {
+ switch (slots[s].buffer_state) {
+ case BufferState::Dequeued:
+ ++dequeued_count;
+ break;
+ case BufferState::Acquired:
+ ++acquired_count;
+ break;
+ case BufferState::Free:
+ // We return the oldest of the free buffers to avoid stalling the producer if
+ // possible, since the consumer may still have pending reads of in-flight buffers
+ if (*found == BufferQueueCore::INVALID_BUFFER_SLOT ||
+ slots[s].frame_number < slots[*found].frame_number) {
+ *found = s;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ // Producers are not allowed to dequeue more than one buffer if they did not set a buffer
+ // count
+ if (!core->override_max_buffer_count && dequeued_count) {
+ LOG_ERROR(Service_NVFlinger,
+ "can't dequeue multiple buffers without setting the buffer count");
+ return Status::InvalidOperation;
+ }
+
+ // See whether a buffer has been queued since the last SetBufferCount so we know whether to
+ // perform the min undequeued buffers check below
+ if (core->buffer_has_been_queued) {
+ // Make sure the producer is not trying to dequeue more buffers than allowed
+ const s32 new_undequeued_count = max_buffer_count - (dequeued_count + 1);
+ const s32 min_undequeued_count = core->GetMinUndequeuedBufferCountLocked(async);
+ if (new_undequeued_count < min_undequeued_count) {
+ LOG_ERROR(Service_NVFlinger,
+ "min undequeued buffer count({}) exceeded (dequeued={} undequeued={})",
+ min_undequeued_count, dequeued_count, new_undequeued_count);
+ return Status::InvalidOperation;
+ }
+ }
+
+ // If we disconnect and reconnect quickly, we can be in a state where our slots are empty
+ // but we have many buffers in the queue. This can cause us to run out of memory if we
+ // outrun the consumer. Wait here if it looks like we have too many buffers queued up.
+ const bool too_many_buffers = core->queue.size() > static_cast<size_t>(max_buffer_count);
+ if (too_many_buffers) {
+ LOG_ERROR(Service_NVFlinger, "queue size is {}, waiting", core->queue.size());
+ }
+
+ // If no buffer is found, or if the queue has too many buffers outstanding, wait for a
+ // buffer to be acquired or released, or for the max buffer count to change.
+ try_again = (*found == BufferQueueCore::INVALID_BUFFER_SLOT) || too_many_buffers;
+ if (try_again) {
+ // Return an error if we're in non-blocking mode (producer and consumer are controlled
+ // by the application).
+ if (core->dequeue_buffer_cannot_block &&
+ (acquired_count <= core->max_acquired_buffer_count)) {
+ return Status::WouldBlock;
+ }
+
+ if (!core->WaitForDequeueCondition()) {
+ // We are no longer running
+ return Status::NoError;
+ }
+ }
+ }
+
+ return Status::NoError;
+}
+
+Status BufferQueueProducer::DequeueBuffer(s32* out_slot, Fence* out_fence, bool async, u32 width,
+ u32 height, PixelFormat format, u32 usage) {
+ LOG_DEBUG(Service_NVFlinger, "async={} w={} h={} format={}, usage={}", async ? "true" : "false",
+ width, height, format, usage);
+
+ if ((width != 0 && height == 0) || (width == 0 && height != 0)) {
+ LOG_ERROR(Service_NVFlinger, "invalid size: w={} h={}", width, height);
+ return Status::BadValue;
+ }
+
+ Status return_flags = Status::NoError;
+ bool attached_by_consumer = false;
+ {
+ std::scoped_lock lock{core->mutex};
+ core->WaitWhileAllocatingLocked();
+
+ if (format == PixelFormat::NoFormat) {
+ format = core->default_buffer_format;
+ }
+
+ // Enable the usage bits the consumer requested
+ usage |= core->consumer_usage_bit;
+
+ s32 found{};
+ Status status = WaitForFreeSlotThenRelock(async, &found, &return_flags);
+ if (status != Status::NoError) {
+ return status;
+ }
+
+ // This should not happen
+ if (found == BufferQueueCore::INVALID_BUFFER_SLOT) {
+ LOG_ERROR(Service_NVFlinger, "no available buffer slots");
+ return Status::Busy;
+ }
+
+ *out_slot = found;
+
+ attached_by_consumer = slots[found].attached_by_consumer;
+
+ const bool use_default_size = !width && !height;
+ if (use_default_size) {
+ width = core->default_width;
+ height = core->default_height;
+ }
+
+ slots[found].buffer_state = BufferState::Dequeued;
+
+ const std::shared_ptr<GraphicBuffer>& buffer(slots[found].graphic_buffer);
+ if ((buffer == nullptr) || (buffer->Width() != width) || (buffer->Height() != height) ||
+ (buffer->Format() != format) || ((buffer->Usage() & usage) != usage)) {
+ slots[found].acquire_called = false;
+ slots[found].graphic_buffer = nullptr;
+ slots[found].request_buffer_called = false;
+ slots[found].fence = Fence::NoFence();
+
+ return_flags |= Status::BufferNeedsReallocation;
+ }
+
+ *out_fence = slots[found].fence;
+ slots[found].fence = Fence::NoFence();
+ }
+
+ if ((return_flags & Status::BufferNeedsReallocation) != Status::None) {
+ LOG_DEBUG(Service_NVFlinger, "allocating a new buffer for slot {}", *out_slot);
+
+ auto graphic_buffer = std::make_shared<GraphicBuffer>(width, height, format, usage);
+ if (graphic_buffer == nullptr) {
+ LOG_ERROR(Service_NVFlinger, "creating GraphicBuffer failed");
+ return Status::NoMemory;
+ }
+
+ {
+ std::scoped_lock lock{core->mutex};
+
+ if (core->is_abandoned) {
+ LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned");
+ return Status::NoInit;
+ }
+
+ slots[*out_slot].frame_number = UINT32_MAX;
+ slots[*out_slot].graphic_buffer = graphic_buffer;
+ }
+ }
+
+ if (attached_by_consumer) {
+ return_flags |= Status::BufferNeedsReallocation;
+ }
+
+ LOG_DEBUG(Service_NVFlinger, "returning slot={} frame={}, flags={}", *out_slot,
+ slots[*out_slot].frame_number, return_flags);
+
+ return return_flags;
+}
+
+Status BufferQueueProducer::DetachBuffer(s32 slot) {
+ LOG_DEBUG(Service_NVFlinger, "slot {}", slot);
+
+ std::scoped_lock lock{core->mutex};
+
+ if (core->is_abandoned) {
+ LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned");
+ return Status::NoInit;
+ }
+
+ if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
+ LOG_ERROR(Service_NVFlinger, "slot {} out of range [0, {})", slot,
+ BufferQueueDefs::NUM_BUFFER_SLOTS);
+ return Status::BadValue;
+ } else if (slots[slot].buffer_state != BufferState::Dequeued) {
+ LOG_ERROR(Service_NVFlinger, "slot {} is not owned by the producer (state = {})", slot,
+ slots[slot].buffer_state);
+ return Status::BadValue;
+ } else if (!slots[slot].request_buffer_called) {
+ LOG_ERROR(Service_NVFlinger, "buffer in slot {} has not been requested", slot);
+ return Status::BadValue;
+ }
+
+ core->FreeBufferLocked(slot);
+ core->SignalDequeueCondition();
+
+ return Status::NoError;
+}
+
+Status BufferQueueProducer::DetachNextBuffer(std::shared_ptr<GraphicBuffer>* out_buffer,
+ Fence* out_fence) {
+ if (out_buffer == nullptr) {
+ LOG_ERROR(Service_NVFlinger, "out_buffer must not be nullptr");
+ return Status::BadValue;
+ } else if (out_fence == nullptr) {
+ LOG_ERROR(Service_NVFlinger, "out_fence must not be nullptr");
+ return Status::BadValue;
+ }
+
+ std::scoped_lock lock{core->mutex};
+ core->WaitWhileAllocatingLocked();
+
+ if (core->is_abandoned) {
+ LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned");
+ return Status::NoInit;
+ }
+
+ // Find the oldest valid slot
+ int found = BufferQueueCore::INVALID_BUFFER_SLOT;
+ for (int s = 0; s < BufferQueueDefs::NUM_BUFFER_SLOTS; ++s) {
+ if (slots[s].buffer_state == BufferState::Free && slots[s].graphic_buffer != nullptr) {
+ if (found == BufferQueueCore::INVALID_BUFFER_SLOT ||
+ slots[s].frame_number < slots[found].frame_number) {
+ found = s;
+ }
+ }
+ }
+
+ if (found == BufferQueueCore::INVALID_BUFFER_SLOT) {
+ return Status::NoMemory;
+ }
+
+ LOG_DEBUG(Service_NVFlinger, "Detached slot {}", found);
+
+ *out_buffer = slots[found].graphic_buffer;
+ *out_fence = slots[found].fence;
+
+ core->FreeBufferLocked(found);
+
+ return Status::NoError;
+}
+
+Status BufferQueueProducer::AttachBuffer(s32* out_slot,
+ const std::shared_ptr<GraphicBuffer>& buffer) {
+ if (out_slot == nullptr) {
+ LOG_ERROR(Service_NVFlinger, "out_slot must not be nullptr");
+ return Status::BadValue;
+ } else if (buffer == nullptr) {
+ LOG_ERROR(Service_NVFlinger, "Cannot attach nullptr buffer");
+ return Status::BadValue;
+ }
+
+ std::scoped_lock lock{core->mutex};
+ core->WaitWhileAllocatingLocked();
+
+ Status return_flags = Status::NoError;
+ s32 found{};
+
+ const auto status = WaitForFreeSlotThenRelock(false, &found, &return_flags);
+ if (status != Status::NoError) {
+ return status;
+ }
+
+ // This should not happen
+ if (found == BufferQueueCore::INVALID_BUFFER_SLOT) {
+ LOG_ERROR(Service_NVFlinger, "No available buffer slots");
+ return Status::Busy;
+ }
+
+ *out_slot = found;
+
+ LOG_DEBUG(Service_NVFlinger, "Returning slot {} flags={}", *out_slot, return_flags);
+
+ slots[*out_slot].graphic_buffer = buffer;
+ slots[*out_slot].buffer_state = BufferState::Dequeued;
+ slots[*out_slot].fence = Fence::NoFence();
+ slots[*out_slot].request_buffer_called = true;
+
+ return return_flags;
+}
+
+Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input,
+ QueueBufferOutput* output) {
+ s64 timestamp{};
+ bool is_auto_timestamp{};
+ Common::Rectangle<s32> crop;
+ NativeWindowScalingMode scaling_mode{};
+ NativeWindowTransform transform;
+ u32 sticky_transform_{};
+ bool async{};
+ s32 swap_interval{};
+ Fence fence{};
+
+ input.Deflate(&timestamp, &is_auto_timestamp, &crop, &scaling_mode, &transform,
+ &sticky_transform_, &async, &swap_interval, &fence);
+
+ switch (scaling_mode) {
+ case NativeWindowScalingMode::Freeze:
+ case NativeWindowScalingMode::ScaleToWindow:
+ case NativeWindowScalingMode::ScaleCrop:
+ case NativeWindowScalingMode::NoScaleCrop:
+ break;
+ default:
+ LOG_ERROR(Service_NVFlinger, "unknown scaling mode {}", scaling_mode);
+ return Status::BadValue;
+ }
+
+ std::shared_ptr<IConsumerListener> frame_available_listener;
+ std::shared_ptr<IConsumerListener> frame_replaced_listener;
+ s32 callback_ticket{};
+ BufferItem item;
+
+ {
+ std::scoped_lock lock{core->mutex};
+
+ if (core->is_abandoned) {
+ LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned");
+ return Status::NoInit;
+ }
+
+ const s32 max_buffer_count = core->GetMaxBufferCountLocked(async);
+ if (async && core->override_max_buffer_count) {
+ if (core->override_max_buffer_count < max_buffer_count) {
+ LOG_ERROR(Service_NVFlinger, "async mode is invalid with "
+ "buffer count override");
+ return Status::BadValue;
+ }
+ }
+
+ if (slot < 0 || slot >= max_buffer_count) {
+ LOG_ERROR(Service_NVFlinger, "slot index {} out of range [0, {})", slot,
+ max_buffer_count);
+ return Status::BadValue;
+ } else if (slots[slot].buffer_state != BufferState::Dequeued) {
+ LOG_ERROR(Service_NVFlinger,
+ "slot {} is not owned by the producer "
+ "(state = {})",
+ slot, slots[slot].buffer_state);
+ return Status::BadValue;
+ } else if (!slots[slot].request_buffer_called) {
+ LOG_ERROR(Service_NVFlinger,
+ "slot {} was queued without requesting "
+ "a buffer",
+ slot);
+ return Status::BadValue;
+ }
+
+ LOG_DEBUG(Service_NVFlinger,
+ "slot={} frame={} time={} crop=[{},{},{},{}] transform={} scale={}", slot,
+ core->frame_counter + 1, timestamp, crop.Left(), crop.Top(), crop.Right(),
+ crop.Bottom(), transform, scaling_mode);
+
+ const std::shared_ptr<GraphicBuffer>& graphic_buffer(slots[slot].graphic_buffer);
+ Common::Rectangle<s32> buffer_rect(graphic_buffer->Width(), graphic_buffer->Height());
+ Common::Rectangle<s32> cropped_rect;
+ [[maybe_unused]] const bool unused = crop.Intersect(buffer_rect, &cropped_rect);
+
+ if (cropped_rect != crop) {
+ LOG_ERROR(Service_NVFlinger, "crop rect is not contained within the buffer in slot {}",
+ slot);
+ return Status::BadValue;
+ }
+
+ slots[slot].fence = fence;
+ slots[slot].buffer_state = BufferState::Queued;
+ ++core->frame_counter;
+ slots[slot].frame_number = core->frame_counter;
+
+ item.acquire_called = slots[slot].acquire_called;
+ item.graphic_buffer = slots[slot].graphic_buffer;
+ item.crop = crop;
+ item.transform = transform & ~NativeWindowTransform::InverseDisplay;
+ item.transform_to_display_inverse =
+ (transform & NativeWindowTransform::InverseDisplay) != NativeWindowTransform::None;
+ item.scaling_mode = static_cast<u32>(scaling_mode);
+ item.timestamp = timestamp;
+ item.is_auto_timestamp = is_auto_timestamp;
+ item.frame_number = core->frame_counter;
+ item.slot = slot;
+ item.fence = fence;
+ item.is_droppable = core->dequeue_buffer_cannot_block || async;
+ item.swap_interval = swap_interval;
+
+ sticky_transform = sticky_transform_;
+
+ if (core->queue.empty()) {
+ // When the queue is empty, we can simply queue this buffer
+ core->queue.push_back(item);
+ frame_available_listener = core->consumer_listener;
+ } else {
+ // When the queue is not empty, we need to look at the front buffer
+ // state to see if we need to replace it
+ auto front(core->queue.begin());
+
+ if (front->is_droppable) {
+ // If the front queued buffer is still being tracked, we first
+ // mark it as freed
+ if (core->StillTracking(*front)) {
+ slots[front->slot].buffer_state = BufferState::Free;
+ // Reset the frame number of the freed buffer so that it is the first in line to
+ // be dequeued again
+ slots[front->slot].frame_number = 0;
+ }
+ // Overwrite the droppable buffer with the incoming one
+ *front = item;
+ frame_replaced_listener = core->consumer_listener;
+ } else {
+ core->queue.push_back(item);
+ frame_available_listener = core->consumer_listener;
+ }
+ }
+
+ core->buffer_has_been_queued = true;
+ core->SignalDequeueCondition();
+ output->Inflate(core->default_width, core->default_height, core->transform_hint,
+ static_cast<u32>(core->queue.size()));
+
+ // Take a ticket for the callback functions
+ callback_ticket = next_callback_ticket++;
+ }
+
+ // Don't send the GraphicBuffer through the callback, and don't send the slot number, since the
+ // consumer shouldn't need it
+ item.graphic_buffer.reset();
+ item.slot = BufferItem::INVALID_BUFFER_SLOT;
+
+ // Call back without the main BufferQueue lock held, but with the callback lock held so we can
+ // ensure that callbacks occur in order
+ {
+ std::scoped_lock lock{callback_mutex};
+ while (callback_ticket != current_callback_ticket) {
+ callback_condition.wait(callback_mutex);
+ }
+
+ if (frame_available_listener != nullptr) {
+ frame_available_listener->OnFrameAvailable(item);
+ } else if (frame_replaced_listener != nullptr) {
+ frame_replaced_listener->OnFrameReplaced(item);
+ }
+
+ ++current_callback_ticket;
+ callback_condition.notify_all();
+ }
+
+ return Status::NoError;
+}
+
+void BufferQueueProducer::CancelBuffer(s32 slot, const Fence& fence) {
+ LOG_DEBUG(Service_NVFlinger, "slot {}", slot);
+
+ std::scoped_lock lock{core->mutex};
+
+ if (core->is_abandoned) {
+ LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned");
+ return;
+ }
+
+ if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
+ LOG_ERROR(Service_NVFlinger, "slot index {} out of range [0, {})", slot,
+ BufferQueueDefs::NUM_BUFFER_SLOTS);
+ return;
+ } else if (slots[slot].buffer_state != BufferState::Dequeued) {
+ LOG_ERROR(Service_NVFlinger, "slot {} is not owned by the producer (state = {})", slot,
+ slots[slot].buffer_state);
+ return;
+ }
+
+ slots[slot].buffer_state = BufferState::Free;
+ slots[slot].frame_number = 0;
+ slots[slot].fence = fence;
+
+ core->SignalDequeueCondition();
+ buffer_wait_event->GetWritableEvent().Signal();
+}
+
+Status BufferQueueProducer::Query(NativeWindow what, s32* out_value) {
+ std::scoped_lock lock{core->mutex};
+
+ if (out_value == nullptr) {
+ LOG_ERROR(Service_NVFlinger, "outValue was nullptr");
+ return Status::BadValue;
+ }
+
+ if (core->is_abandoned) {
+ LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned");
+ return Status::NoInit;
+ }
+
+ u32 value{};
+ switch (what) {
+ case NativeWindow::Width:
+ value = core->default_width;
+ break;
+ case NativeWindow::Height:
+ value = core->default_height;
+ break;
+ case NativeWindow::Format:
+ value = static_cast<u32>(core->default_buffer_format);
+ break;
+ case NativeWindow::MinUndequeedBuffers:
+ value = core->GetMinUndequeuedBufferCountLocked(false);
+ break;
+ case NativeWindow::StickyTransform:
+ value = sticky_transform;
+ break;
+ case NativeWindow::ConsumerRunningBehind:
+ value = (core->queue.size() > 1);
+ break;
+ case NativeWindow::ConsumerUsageBits:
+ value = core->consumer_usage_bit;
+ break;
+ default:
+ UNREACHABLE();
+ return Status::BadValue;
+ }
+
+ LOG_DEBUG(Service_NVFlinger, "what = {}, value = {}", what, value);
+
+ *out_value = static_cast<s32>(value);
+
+ return Status::NoError;
+}
+
+Status BufferQueueProducer::Connect(const std::shared_ptr<IProducerListener>& listener,
+ NativeWindowApi api, bool producer_controlled_by_app,
+ QueueBufferOutput* output) {
+ std::scoped_lock lock{core->mutex};
+
+ LOG_DEBUG(Service_NVFlinger, "api = {} producer_controlled_by_app = {}", api,
+ producer_controlled_by_app);
+
+ if (core->is_abandoned) {
+ LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned");
+ return Status::NoInit;
+ }
+
+ if (core->consumer_listener == nullptr) {
+ LOG_ERROR(Service_NVFlinger, "BufferQueue has no consumer");
+ return Status::NoInit;
+ }
+
+ if (output == nullptr) {
+ LOG_ERROR(Service_NVFlinger, "output was nullptr");
+ return Status::BadValue;
+ }
+
+ if (core->connected_api != NativeWindowApi::NoConnectedApi) {
+ LOG_ERROR(Service_NVFlinger, "already connected (cur = {} req = {})", core->connected_api,
+ api);
+ return Status::BadValue;
+ }
+
+ Status status = Status::NoError;
+ switch (api) {
+ case NativeWindowApi::Egl:
+ case NativeWindowApi::Cpu:
+ case NativeWindowApi::Media:
+ case NativeWindowApi::Camera:
+ core->connected_api = api;
+ output->Inflate(core->default_width, core->default_height, core->transform_hint,
+ static_cast<u32>(core->queue.size()));
+ core->connected_producer_listener = listener;
+ break;
+ default:
+ LOG_ERROR(Service_NVFlinger, "unknown api = {}", api);
+ status = Status::BadValue;
+ break;
+ }
+
+ core->buffer_has_been_queued = false;
+ core->dequeue_buffer_cannot_block =
+ core->consumer_controlled_by_app && producer_controlled_by_app;
+
+ return status;
+}
+
+Status BufferQueueProducer::Disconnect(NativeWindowApi api) {
+ LOG_DEBUG(Service_NVFlinger, "api = {}", api);
+
+ Status status = Status::NoError;
+ std::shared_ptr<IConsumerListener> listener;
+
+ {
+ std::scoped_lock lock{core->mutex};
+
+ core->WaitWhileAllocatingLocked();
+
+ if (core->is_abandoned) {
+ // Disconnecting after the surface has been abandoned is a no-op.
+ return Status::NoError;
+ }
+
+ switch (api) {
+ case NativeWindowApi::Egl:
+ case NativeWindowApi::Cpu:
+ case NativeWindowApi::Media:
+ case NativeWindowApi::Camera:
+ if (core->connected_api == api) {
+ core->FreeAllBuffersLocked();
+ core->connected_producer_listener = nullptr;
+ core->connected_api = NativeWindowApi::NoConnectedApi;
+ core->SignalDequeueCondition();
+ buffer_wait_event->GetWritableEvent().Signal();
+ listener = core->consumer_listener;
+ } else {
+ LOG_ERROR(Service_NVFlinger, "still connected to another api (cur = {} req = {})",
+ core->connected_api, api);
+ status = Status::BadValue;
+ }
+ break;
+ default:
+ LOG_ERROR(Service_NVFlinger, "unknown api = {}", api);
+ status = Status::BadValue;
+ break;
+ }
+ }
+
+ // Call back without lock held
+ if (listener != nullptr) {
+ listener->OnBuffersReleased();
+ }
+
+ return status;
+}
+
+Status BufferQueueProducer::SetPreallocatedBuffer(s32 slot,
+ const std::shared_ptr<GraphicBuffer>& buffer) {
+ LOG_DEBUG(Service_NVFlinger, "slot {}", slot);
+
+ if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
+ return Status::BadValue;
+ }
+
+ std::scoped_lock lock{core->mutex};
+
+ slots[slot] = {};
+ slots[slot].graphic_buffer = buffer;
+ slots[slot].frame_number = 0;
+
+ // Most games preallocate a buffer and pass a valid buffer here. However, it is possible for
+ // this to be called with an empty buffer, Naruto Ultimate Ninja Storm is a game that does this.
+ if (buffer) {
+ slots[slot].is_preallocated = true;
+
+ core->override_max_buffer_count = core->GetPreallocatedBufferCountLocked();
+ core->default_width = buffer->Width();
+ core->default_height = buffer->Height();
+ core->default_buffer_format = buffer->Format();
+ }
+
+ core->SignalDequeueCondition();
+ buffer_wait_event->GetWritableEvent().Signal();
+
+ return Status::NoError;
+}
+
+void BufferQueueProducer::Transact(Kernel::HLERequestContext& ctx, TransactionId code, u32 flags) {
+ Status status{Status::NoError};
+ Parcel parcel_in{ctx.ReadBuffer()};
+ Parcel parcel_out{};
+
+ switch (code) {
+ case TransactionId::Connect: {
+ const auto enable_listener = parcel_in.Read<bool>();
+ const auto api = parcel_in.Read<NativeWindowApi>();
+ const auto producer_controlled_by_app = parcel_in.Read<bool>();
+
+ UNIMPLEMENTED_IF_MSG(enable_listener, "Listener is unimplemented!");
+
+ std::shared_ptr<IProducerListener> listener;
+ QueueBufferOutput output{};
+
+ status = Connect(listener, api, producer_controlled_by_app, &output);
+
+ parcel_out.Write(output);
+ break;
+ }
+ case TransactionId::SetPreallocatedBuffer: {
+ const auto slot = parcel_in.Read<s32>();
+ const auto buffer = parcel_in.ReadObject<GraphicBuffer>();
+
+ status = SetPreallocatedBuffer(slot, buffer);
+ break;
+ }
+ case TransactionId::DequeueBuffer: {
+ const auto is_async = parcel_in.Read<bool>();
+ const auto width = parcel_in.Read<u32>();
+ const auto height = parcel_in.Read<u32>();
+ const auto pixel_format = parcel_in.Read<PixelFormat>();
+ const auto usage = parcel_in.Read<u32>();
+
+ s32 slot{};
+ Fence fence{};
+
+ status = DequeueBuffer(&slot, &fence, is_async, width, height, pixel_format, usage);
+
+ parcel_out.Write(slot);
+ parcel_out.WriteObject(&fence);
+ break;
+ }
+ case TransactionId::RequestBuffer: {
+ const auto slot = parcel_in.Read<s32>();
+
+ std::shared_ptr<GraphicBuffer> buf;
+
+ status = RequestBuffer(slot, &buf);
+
+ parcel_out.WriteObject(buf);
+ break;
+ }
+ case TransactionId::QueueBuffer: {
+ const auto slot = parcel_in.Read<s32>();
+
+ QueueBufferInput input{parcel_in};
+ QueueBufferOutput output;
+
+ status = QueueBuffer(slot, input, &output);
+
+ parcel_out.Write(output);
+ break;
+ }
+ case TransactionId::Query: {
+ const auto what = parcel_in.Read<NativeWindow>();
+
+ s32 value{};
+
+ status = Query(what, &value);
+
+ parcel_out.Write(value);
+ break;
+ }
+ case TransactionId::CancelBuffer: {
+ const auto slot = parcel_in.Read<s32>();
+ const auto fence = parcel_in.ReadFlattened<Fence>();
+
+ CancelBuffer(slot, fence);
+ break;
+ }
+ case TransactionId::Disconnect: {
+ const auto api = parcel_in.Read<NativeWindowApi>();
+
+ status = Disconnect(api);
+ break;
+ }
+ case TransactionId::DetachBuffer: {
+ const auto slot = parcel_in.Read<s32>();
+
+ status = DetachBuffer(slot);
+ break;
+ }
+ case TransactionId::SetBufferCount: {
+ const auto buffer_count = parcel_in.Read<s32>();
+
+ status = SetBufferCount(buffer_count);
+ break;
+ }
+ case TransactionId::GetBufferHistory:
+ LOG_WARNING(Service_NVFlinger, "(STUBBED) called, transaction=GetBufferHistory");
+ break;
+ default:
+ ASSERT_MSG(false, "Unimplemented TransactionId {}", code);
+ break;
+ }
+
+ parcel_out.Write(status);
+
+ ctx.WriteBuffer(parcel_out.Serialize());
+}
+
+Kernel::KReadableEvent& BufferQueueProducer::GetNativeHandle() {
+ return buffer_wait_event->GetReadableEvent();
+}
+
+} // namespace Service::android
diff --git a/src/core/hle/service/nvflinger/buffer_queue_producer.h b/src/core/hle/service/nvflinger/buffer_queue_producer.h
new file mode 100644
index 000000000..c4ca68fd3
--- /dev/null
+++ b/src/core/hle/service/nvflinger/buffer_queue_producer.h
@@ -0,0 +1,83 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Copyright 2021 yuzu Emulator Project
+// Copyright 2014 The Android Open Source Project
+// Parts of this implementation were base on:
+// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferQueueProducer.h
+
+#pragma once
+
+#include <condition_variable>
+#include <memory>
+#include <mutex>
+
+#include "common/common_funcs.h"
+#include "core/hle/service/nvdrv/nvdata.h"
+#include "core/hle/service/nvflinger/binder.h"
+#include "core/hle/service/nvflinger/buffer_queue_defs.h"
+#include "core/hle/service/nvflinger/buffer_slot.h"
+#include "core/hle/service/nvflinger/graphic_buffer_producer.h"
+#include "core/hle/service/nvflinger/pixel_format.h"
+#include "core/hle/service/nvflinger/status.h"
+#include "core/hle/service/nvflinger/window.h"
+
+namespace Kernel {
+class KernelCore;
+class KEvent;
+class KReadableEvent;
+class KWritableEvent;
+} // namespace Kernel
+
+namespace Service::KernelHelpers {
+class ServiceContext;
+} // namespace Service::KernelHelpers
+
+namespace Service::android {
+
+class BufferQueueCore;
+class IProducerListener;
+
+class BufferQueueProducer final : public IBinder {
+public:
+ explicit BufferQueueProducer(Service::KernelHelpers::ServiceContext& service_context_,
+ std::shared_ptr<BufferQueueCore> buffer_queue_core_);
+ ~BufferQueueProducer();
+
+ void Transact(Kernel::HLERequestContext& ctx, android::TransactionId code, u32 flags) override;
+
+ Kernel::KReadableEvent& GetNativeHandle() override;
+
+public:
+ Status RequestBuffer(s32 slot, std::shared_ptr<GraphicBuffer>* buf);
+ Status SetBufferCount(s32 buffer_count);
+ Status DequeueBuffer(s32* out_slot, android::Fence* out_fence, bool async, u32 width,
+ u32 height, PixelFormat format, u32 usage);
+ Status DetachBuffer(s32 slot);
+ Status DetachNextBuffer(std::shared_ptr<GraphicBuffer>* out_buffer, Fence* out_fence);
+ Status AttachBuffer(s32* outSlot, const std::shared_ptr<GraphicBuffer>& buffer);
+ Status QueueBuffer(s32 slot, const QueueBufferInput& input, QueueBufferOutput* output);
+ void CancelBuffer(s32 slot, const Fence& fence);
+ Status Query(NativeWindow what, s32* out_value);
+ Status Connect(const std::shared_ptr<IProducerListener>& listener, NativeWindowApi api,
+ bool producer_controlled_by_app, QueueBufferOutput* output);
+
+ Status Disconnect(NativeWindowApi api);
+ Status SetPreallocatedBuffer(s32 slot, const std::shared_ptr<GraphicBuffer>& buffer);
+
+private:
+ BufferQueueProducer(const BufferQueueProducer&) = delete;
+
+ Status WaitForFreeSlotThenRelock(bool async, s32* found, Status* return_flags) const;
+
+ Kernel::KEvent* buffer_wait_event{};
+ Service::KernelHelpers::ServiceContext& service_context;
+
+ std::shared_ptr<BufferQueueCore> core;
+ BufferQueueDefs::SlotsType& slots;
+ u32 sticky_transform{};
+ std::mutex callback_mutex;
+ s32 next_callback_ticket{};
+ s32 current_callback_ticket{};
+ std::condition_variable_any callback_condition;
+};
+
+} // namespace Service::android
diff --git a/src/core/hle/service/nvflinger/buffer_slot.h b/src/core/hle/service/nvflinger/buffer_slot.h
new file mode 100644
index 000000000..e3ea58910
--- /dev/null
+++ b/src/core/hle/service/nvflinger/buffer_slot.h
@@ -0,0 +1,39 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Copyright 2021 yuzu Emulator Project
+// Copyright 2014 The Android Open Source Project
+// Parts of this implementation were base on:
+// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferSlot.h
+
+#pragma once
+
+#include <memory>
+
+#include "common/common_types.h"
+#include "core/hle/service/nvflinger/ui/fence.h"
+
+namespace Service::android {
+
+class GraphicBuffer;
+
+enum class BufferState : u32 {
+ Free = 0,
+ Dequeued = 1,
+ Queued = 2,
+ Acquired = 3,
+};
+
+struct BufferSlot final {
+ constexpr BufferSlot() = default;
+
+ std::shared_ptr<GraphicBuffer> graphic_buffer;
+ BufferState buffer_state{BufferState::Free};
+ bool request_buffer_called{};
+ u64 frame_number{};
+ Fence fence;
+ bool acquire_called{};
+ bool needs_cleanup_on_release{};
+ bool attached_by_consumer{};
+ bool is_preallocated{};
+};
+
+} // namespace Service::android
diff --git a/src/core/hle/service/nvflinger/buffer_transform_flags.h b/src/core/hle/service/nvflinger/buffer_transform_flags.h
new file mode 100644
index 000000000..e8e6300e3
--- /dev/null
+++ b/src/core/hle/service/nvflinger/buffer_transform_flags.h
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Copyright 2021 yuzu Emulator Project
+
+#pragma once
+
+#include "common/common_types.h"
+
+namespace Service::android {
+
+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)
+ FlipV = 0x02,
+ /// Rotate source image 90 degrees clockwise
+ Rotate90 = 0x04,
+ /// Rotate source image 180 degrees
+ Rotate180 = 0x03,
+ /// Rotate source image 270 degrees clockwise
+ Rotate270 = 0x07,
+};
+
+} // namespace Service::android
diff --git a/src/core/hle/service/nvflinger/consumer_base.cpp b/src/core/hle/service/nvflinger/consumer_base.cpp
new file mode 100644
index 000000000..30fc21acc
--- /dev/null
+++ b/src/core/hle/service/nvflinger/consumer_base.cpp
@@ -0,0 +1,133 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Copyright 2021 yuzu Emulator Project
+// Copyright 2010 The Android Open Source Project
+// Parts of this implementation were base on:
+// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/ConsumerBase.cpp
+
+#include "common/assert.h"
+#include "common/logging/log.h"
+#include "core/hle/service/nvflinger/buffer_item.h"
+#include "core/hle/service/nvflinger/buffer_queue_consumer.h"
+#include "core/hle/service/nvflinger/buffer_queue_core.h"
+#include "core/hle/service/nvflinger/consumer_base.h"
+#include "core/hle/service/nvflinger/ui/graphic_buffer.h"
+
+namespace Service::android {
+
+ConsumerBase::ConsumerBase(std::unique_ptr<BufferQueueConsumer> consumer_)
+ : consumer{std::move(consumer_)} {}
+
+ConsumerBase::~ConsumerBase() {
+ std::scoped_lock lock{mutex};
+
+ ASSERT_MSG(is_abandoned, "consumer is not abandoned!");
+}
+
+void ConsumerBase::Connect(bool controlled_by_app) {
+ consumer->Connect(shared_from_this(), controlled_by_app);
+}
+
+void ConsumerBase::FreeBufferLocked(s32 slot_index) {
+ LOG_DEBUG(Service_NVFlinger, "slot_index={}", slot_index);
+
+ slots[slot_index].graphic_buffer = nullptr;
+ slots[slot_index].fence = Fence::NoFence();
+ slots[slot_index].frame_number = 0;
+}
+
+void ConsumerBase::OnFrameAvailable(const BufferItem& item) {
+ LOG_DEBUG(Service_NVFlinger, "called");
+}
+
+void ConsumerBase::OnFrameReplaced(const BufferItem& item) {
+ LOG_DEBUG(Service_NVFlinger, "called");
+}
+
+void ConsumerBase::OnBuffersReleased() {
+ std::scoped_lock lock{mutex};
+
+ LOG_DEBUG(Service_NVFlinger, "called");
+
+ if (is_abandoned) {
+ // Nothing to do if we're already abandoned.
+ return;
+ }
+
+ u64 mask = 0;
+ consumer->GetReleasedBuffers(&mask);
+ for (int i = 0; i < BufferQueueDefs::NUM_BUFFER_SLOTS; i++) {
+ if (mask & (1ULL << i)) {
+ FreeBufferLocked(i);
+ }
+ }
+}
+
+void ConsumerBase::OnSidebandStreamChanged() {}
+
+Status ConsumerBase::AcquireBufferLocked(BufferItem* item, std::chrono::nanoseconds present_when) {
+ Status err = consumer->AcquireBuffer(item, present_when);
+ if (err != Status::NoError) {
+ return err;
+ }
+
+ if (item->graphic_buffer != nullptr) {
+ slots[item->slot].graphic_buffer = item->graphic_buffer;
+ }
+
+ slots[item->slot].frame_number = item->frame_number;
+ slots[item->slot].fence = item->fence;
+
+ LOG_DEBUG(Service_NVFlinger, "slot={}", item->slot);
+
+ return Status::NoError;
+}
+
+Status ConsumerBase::AddReleaseFenceLocked(s32 slot,
+ const std::shared_ptr<GraphicBuffer> graphic_buffer,
+ const Fence& fence) {
+ LOG_DEBUG(Service_NVFlinger, "slot={}", slot);
+
+ // If consumer no longer tracks this graphic_buffer, we can safely
+ // drop this fence, as it will never be received by the producer.
+
+ if (!StillTracking(slot, graphic_buffer)) {
+ return Status::NoError;
+ }
+
+ slots[slot].fence = fence;
+
+ return Status::NoError;
+}
+
+Status ConsumerBase::ReleaseBufferLocked(s32 slot,
+ const std::shared_ptr<GraphicBuffer> graphic_buffer) {
+ // If consumer no longer tracks this graphic_buffer (we received a new
+ // buffer on the same slot), the buffer producer is definitely no longer
+ // tracking it.
+
+ if (!StillTracking(slot, graphic_buffer)) {
+ return Status::NoError;
+ }
+
+ LOG_DEBUG(Service_NVFlinger, "slot={}", slot);
+ Status err = consumer->ReleaseBuffer(slot, slots[slot].frame_number, slots[slot].fence);
+ if (err == Status::StaleBufferSlot) {
+ FreeBufferLocked(slot);
+ }
+
+ slots[slot].fence = Fence::NoFence();
+
+ return err;
+}
+
+bool ConsumerBase::StillTracking(s32 slot,
+ const std::shared_ptr<GraphicBuffer> graphic_buffer) const {
+ if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
+ return false;
+ }
+
+ return (slots[slot].graphic_buffer != nullptr &&
+ slots[slot].graphic_buffer->Handle() == graphic_buffer->Handle());
+}
+
+} // namespace Service::android
diff --git a/src/core/hle/service/nvflinger/consumer_base.h b/src/core/hle/service/nvflinger/consumer_base.h
new file mode 100644
index 000000000..736080e3a
--- /dev/null
+++ b/src/core/hle/service/nvflinger/consumer_base.h
@@ -0,0 +1,60 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Copyright 2021 yuzu Emulator Project
+// Copyright 2010 The Android Open Source Project
+// Parts of this implementation were base on:
+// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/ConsumerBase.h
+
+#pragma once
+
+#include <array>
+#include <chrono>
+#include <memory>
+#include <mutex>
+
+#include "common/common_types.h"
+#include "core/hle/service/nvflinger/buffer_queue_defs.h"
+#include "core/hle/service/nvflinger/consumer_listener.h"
+#include "core/hle/service/nvflinger/status.h"
+
+namespace Service::android {
+
+class BufferItem;
+class BufferQueueConsumer;
+
+class ConsumerBase : public IConsumerListener, public std::enable_shared_from_this<ConsumerBase> {
+public:
+ void Connect(bool controlled_by_app);
+
+protected:
+ explicit ConsumerBase(std::unique_ptr<BufferQueueConsumer> consumer_);
+ virtual ~ConsumerBase();
+
+ virtual void OnFrameAvailable(const BufferItem& item) override;
+ virtual void OnFrameReplaced(const BufferItem& item) override;
+ virtual void OnBuffersReleased() override;
+ virtual void OnSidebandStreamChanged() override;
+
+ void FreeBufferLocked(s32 slot_index);
+ Status AcquireBufferLocked(BufferItem* item, std::chrono::nanoseconds present_when);
+ Status ReleaseBufferLocked(s32 slot, const std::shared_ptr<GraphicBuffer> graphic_buffer);
+ bool StillTracking(s32 slot, const std::shared_ptr<GraphicBuffer> graphic_buffer) const;
+ Status AddReleaseFenceLocked(s32 slot, const std::shared_ptr<GraphicBuffer> graphic_buffer,
+ const Fence& fence);
+
+ struct Slot final {
+ std::shared_ptr<GraphicBuffer> graphic_buffer;
+ Fence fence;
+ u64 frame_number{};
+ };
+
+protected:
+ std::array<Slot, BufferQueueDefs::NUM_BUFFER_SLOTS> slots;
+
+ bool is_abandoned{};
+
+ std::unique_ptr<BufferQueueConsumer> consumer;
+
+ mutable std::mutex mutex;
+};
+
+} // namespace Service::android
diff --git a/src/core/hle/service/nvflinger/consumer_listener.h b/src/core/hle/service/nvflinger/consumer_listener.h
new file mode 100644
index 000000000..b6d1c3e9a
--- /dev/null
+++ b/src/core/hle/service/nvflinger/consumer_listener.h
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Copyright 2021 yuzu Emulator Project
+// Copyright 2014 The Android Open Source Project
+// Parts of this implementation were base on:
+// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/IConsumerListener.h
+
+#pragma once
+
+namespace Service::android {
+
+class BufferItem;
+
+/// ConsumerListener is the interface through which the BufferQueue notifies the consumer of events
+/// that the consumer may wish to react to.
+class IConsumerListener {
+public:
+ IConsumerListener() = default;
+ virtual ~IConsumerListener() = default;
+
+ virtual void OnFrameAvailable(const BufferItem& item) = 0;
+ virtual void OnFrameReplaced(const BufferItem& item) = 0;
+ virtual void OnBuffersReleased() = 0;
+ virtual void OnSidebandStreamChanged() = 0;
+};
+
+}; // namespace Service::android
diff --git a/src/core/hle/service/nvflinger/graphic_buffer_producer.cpp b/src/core/hle/service/nvflinger/graphic_buffer_producer.cpp
new file mode 100644
index 000000000..04068827e
--- /dev/null
+++ b/src/core/hle/service/nvflinger/graphic_buffer_producer.cpp
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Copyright 2021 yuzu Emulator Project
+// Copyright 2010 The Android Open Source Project
+// Parts of this implementation were base on:
+// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/IGraphicBufferProducer.cpp
+
+#include "core/hle/service/nvflinger/graphic_buffer_producer.h"
+#include "core/hle/service/nvflinger/parcel.h"
+
+namespace Service::android {
+
+QueueBufferInput::QueueBufferInput(Parcel& parcel) {
+ parcel.ReadFlattened(*this);
+}
+
+QueueBufferOutput::QueueBufferOutput() = default;
+
+} // namespace Service::android
diff --git a/src/core/hle/service/nvflinger/graphic_buffer_producer.h b/src/core/hle/service/nvflinger/graphic_buffer_producer.h
new file mode 100644
index 000000000..98d27871c
--- /dev/null
+++ b/src/core/hle/service/nvflinger/graphic_buffer_producer.h
@@ -0,0 +1,76 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Copyright 2021 yuzu Emulator Project
+// Copyright 2010 The Android Open Source Project
+// Parts of this implementation were base on:
+// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/IGraphicBufferProducer.h
+
+#pragma once
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "common/math_util.h"
+#include "core/hle/service/nvflinger/ui/fence.h"
+#include "core/hle/service/nvflinger/window.h"
+
+namespace Service::android {
+
+class Parcel;
+
+#pragma pack(push, 1)
+struct QueueBufferInput final {
+ explicit QueueBufferInput(Parcel& parcel);
+
+ void Deflate(s64* timestamp_, bool* is_auto_timestamp_, Common::Rectangle<s32>* crop_,
+ NativeWindowScalingMode* scaling_mode_, NativeWindowTransform* transform_,
+ u32* sticky_transform_, bool* async_, s32* swap_interval_, Fence* fence_) const {
+ *timestamp_ = timestamp;
+ *is_auto_timestamp_ = static_cast<bool>(is_auto_timestamp);
+ *crop_ = crop;
+ *scaling_mode_ = scaling_mode;
+ *transform_ = transform;
+ *sticky_transform_ = sticky_transform;
+ *async_ = static_cast<bool>(async);
+ *swap_interval_ = swap_interval;
+ *fence_ = fence;
+ }
+
+private:
+ s64 timestamp{};
+ s32 is_auto_timestamp{};
+ Common::Rectangle<s32> crop{};
+ NativeWindowScalingMode scaling_mode{};
+ NativeWindowTransform transform{};
+ u32 sticky_transform{};
+ s32 async{};
+ s32 swap_interval{};
+ Fence fence{};
+};
+#pragma pack(pop)
+static_assert(sizeof(QueueBufferInput) == 84, "QueueBufferInput has wrong size");
+
+struct QueueBufferOutput final {
+ QueueBufferOutput();
+
+ void Deflate(u32* width_, u32* height_, u32* transform_hint_, u32* num_pending_buffers_) const {
+ *width_ = width;
+ *height_ = height;
+ *transform_hint_ = transform_hint;
+ *num_pending_buffers_ = num_pending_buffers;
+ }
+
+ void Inflate(u32 width_, u32 height_, u32 transform_hint_, u32 num_pending_buffers_) {
+ width = width_;
+ height = height_;
+ transform_hint = transform_hint_;
+ num_pending_buffers = num_pending_buffers_;
+ }
+
+private:
+ u32 width{};
+ u32 height{};
+ u32 transform_hint{};
+ u32 num_pending_buffers{};
+};
+static_assert(sizeof(QueueBufferOutput) == 16, "QueueBufferOutput has wrong size");
+
+} // namespace Service::android
diff --git a/src/core/hle/service/nvflinger/hos_binder_driver_server.cpp b/src/core/hle/service/nvflinger/hos_binder_driver_server.cpp
new file mode 100644
index 000000000..094ba2542
--- /dev/null
+++ b/src/core/hle/service/nvflinger/hos_binder_driver_server.cpp
@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Copyright 2021 yuzu Emulator Project
+
+#include <mutex>
+
+#include "common/common_types.h"
+#include "core/hle/service/nvflinger/hos_binder_driver_server.h"
+
+namespace Service::NVFlinger {
+
+HosBinderDriverServer::HosBinderDriverServer(Core::System& system_)
+ : service_context(system_, "HosBinderDriverServer") {}
+
+HosBinderDriverServer::~HosBinderDriverServer() {}
+
+u64 HosBinderDriverServer::RegisterProducer(std::unique_ptr<android::IBinder>&& binder) {
+ std::scoped_lock lk{lock};
+
+ last_id++;
+
+ producers[last_id] = std::move(binder);
+
+ return last_id;
+}
+
+android::IBinder* HosBinderDriverServer::TryGetProducer(u64 id) {
+ std::scoped_lock lk{lock};
+
+ if (auto search = producers.find(id); search != producers.end()) {
+ return search->second.get();
+ }
+
+ return {};
+}
+
+} // namespace Service::NVFlinger
diff --git a/src/core/hle/service/nvflinger/hos_binder_driver_server.h b/src/core/hle/service/nvflinger/hos_binder_driver_server.h
new file mode 100644
index 000000000..cbca87fa0
--- /dev/null
+++ b/src/core/hle/service/nvflinger/hos_binder_driver_server.h
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Copyright 2021 yuzu Emulator Project
+
+#pragma once
+
+#include <memory>
+#include <mutex>
+#include <unordered_map>
+
+#include "common/common_types.h"
+#include "core/hle/service/kernel_helpers.h"
+#include "core/hle/service/nvflinger/binder.h"
+
+namespace Core {
+class System;
+}
+
+namespace Service::NVFlinger {
+
+class HosBinderDriverServer final {
+public:
+ explicit HosBinderDriverServer(Core::System& system_);
+ ~HosBinderDriverServer();
+
+ u64 RegisterProducer(std::unique_ptr<android::IBinder>&& binder);
+
+ android::IBinder* TryGetProducer(u64 id);
+
+private:
+ KernelHelpers::ServiceContext service_context;
+
+ std::unordered_map<u64, std::unique_ptr<android::IBinder>> producers;
+ std::mutex lock;
+ u64 last_id{};
+};
+
+} // namespace Service::NVFlinger
diff --git a/src/core/hle/service/nvflinger/nvflinger.cpp b/src/core/hle/service/nvflinger/nvflinger.cpp
index 01e69de30..6fb2cdff1 100644
--- a/src/core/hle/service/nvflinger/nvflinger.cpp
+++ b/src/core/hle/service/nvflinger/nvflinger.cpp
@@ -1,6 +1,5 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Copyright 2021 yuzu Emulator Project
#include <algorithm>
#include <optional>
@@ -16,8 +15,11 @@
#include "core/hle/kernel/k_readable_event.h"
#include "core/hle/service/nvdrv/devices/nvdisp_disp0.h"
#include "core/hle/service/nvdrv/nvdrv.h"
-#include "core/hle/service/nvflinger/buffer_queue.h"
+#include "core/hle/service/nvflinger/buffer_item_consumer.h"
+#include "core/hle/service/nvflinger/buffer_queue_core.h"
+#include "core/hle/service/nvflinger/hos_binder_driver_server.h"
#include "core/hle/service/nvflinger/nvflinger.h"
+#include "core/hle/service/nvflinger/ui/graphic_buffer.h"
#include "core/hle/service/vi/display/vi_display.h"
#include "core/hle/service/vi/layer/vi_layer.h"
#include "video_core/gpu.h"
@@ -53,13 +55,14 @@ void NVFlinger::SplitVSync(std::stop_token stop_token) {
}
}
-NVFlinger::NVFlinger(Core::System& system_)
- : system(system_), service_context(system_, "nvflinger") {
- displays.emplace_back(0, "Default", service_context, system);
- displays.emplace_back(1, "External", service_context, system);
- displays.emplace_back(2, "Edid", service_context, system);
- displays.emplace_back(3, "Internal", service_context, system);
- displays.emplace_back(4, "Null", service_context, system);
+NVFlinger::NVFlinger(Core::System& system_, HosBinderDriverServer& hos_binder_driver_server_)
+ : system(system_), service_context(system_, "nvflinger"),
+ hos_binder_driver_server(hos_binder_driver_server_) {
+ displays.emplace_back(0, "Default", hos_binder_driver_server, service_context, system);
+ displays.emplace_back(1, "External", hos_binder_driver_server, service_context, system);
+ displays.emplace_back(2, "Edid", hos_binder_driver_server, service_context, system);
+ displays.emplace_back(3, "Internal", hos_binder_driver_server, service_context, system);
+ displays.emplace_back(4, "Null", hos_binder_driver_server, service_context, system);
guard = std::make_shared<std::mutex>();
// Schedule the screen composition events
@@ -83,12 +86,15 @@ NVFlinger::NVFlinger(Core::System& system_)
}
NVFlinger::~NVFlinger() {
- for (auto& buffer_queue : buffer_queues) {
- buffer_queue->Disconnect();
- }
if (!system.IsMulticore()) {
system.CoreTiming().UnscheduleEvent(composition_event, 0);
}
+
+ for (auto& display : displays) {
+ for (size_t layer = 0; layer < display.GetNumLayers(); ++layer) {
+ display.GetLayer(layer).Core().NotifyShutdown();
+ }
+ }
}
void NVFlinger::SetNVDrvInstance(std::shared_ptr<Nvidia::Module> instance) {
@@ -98,7 +104,7 @@ void NVFlinger::SetNVDrvInstance(std::shared_ptr<Nvidia::Module> instance) {
std::optional<u64> NVFlinger::OpenDisplay(std::string_view name) {
const auto lock_guard = Lock();
- LOG_DEBUG(Service, "Opening \"{}\" display", name);
+ LOG_DEBUG(Service_NVFlinger, "Opening \"{}\" display", name);
const auto itr =
std::find_if(displays.begin(), displays.end(),
@@ -125,10 +131,8 @@ std::optional<u64> NVFlinger::CreateLayer(u64 display_id) {
}
void NVFlinger::CreateLayerAtId(VI::Display& display, u64 layer_id) {
- const u32 buffer_queue_id = next_buffer_queue_id++;
- buffer_queues.emplace_back(
- std::make_unique<BufferQueue>(system.Kernel(), buffer_queue_id, layer_id, service_context));
- display.CreateLayer(layer_id, *buffer_queues.back());
+ const auto buffer_id = next_buffer_queue_id++;
+ display.CreateLayer(layer_id, buffer_id);
}
void NVFlinger::CloseLayer(u64 layer_id) {
@@ -147,7 +151,7 @@ std::optional<u32> NVFlinger::FindBufferQueueId(u64 display_id, u64 layer_id) {
return std::nullopt;
}
- return layer->GetBufferQueue().GetId();
+ return layer->GetBinderId();
}
Kernel::KReadableEvent* NVFlinger::FindVsyncEvent(u64 display_id) {
@@ -161,18 +165,6 @@ Kernel::KReadableEvent* NVFlinger::FindVsyncEvent(u64 display_id) {
return &display->GetVSyncEvent();
}
-BufferQueue* NVFlinger::FindBufferQueue(u32 id) {
- const auto lock_guard = Lock();
- const auto itr = std::find_if(buffer_queues.begin(), buffer_queues.end(),
- [id](const auto& queue) { return queue->GetId() == id; });
-
- if (itr == buffer_queues.end()) {
- return nullptr;
- }
-
- return itr->get();
-}
-
VI::Display* NVFlinger::FindDisplay(u64 display_id) {
const auto itr =
std::find_if(displays.begin(), displays.end(),
@@ -227,7 +219,7 @@ VI::Layer* NVFlinger::FindOrCreateLayer(u64 display_id, u64 layer_id) {
auto* layer = display->FindLayer(layer_id);
if (layer == nullptr) {
- LOG_DEBUG(Service, "Layer at id {} not found. Trying to create it.", layer_id);
+ LOG_DEBUG(Service_NVFlinger, "Layer at id {} not found. Trying to create it.", layer_id);
CreateLayerAtId(*display, layer_id);
return display->FindLayer(layer_id);
}
@@ -246,23 +238,22 @@ void NVFlinger::Compose() {
// TODO(Subv): Support more than 1 layer.
VI::Layer& layer = display.GetLayer(0);
- auto& buffer_queue = layer.GetBufferQueue();
- // Search for a queued buffer and acquire it
- auto buffer = buffer_queue.AcquireBuffer();
+ android::BufferItem buffer{};
+ const auto status = layer.GetConsumer().AcquireBuffer(&buffer, {}, false);
- if (!buffer) {
+ if (status != android::Status::NoError) {
continue;
}
- const auto& igbp_buffer = buffer->get().igbp_buffer;
+ const auto& igbp_buffer = *buffer.graphic_buffer;
if (!system.IsPoweredOn()) {
return; // We are likely shutting down
}
auto& gpu = system.GPU();
- const auto& multi_fence = buffer->get().multi_fence;
+ const auto& multi_fence = buffer.fence;
guard->unlock();
for (u32 fence_id = 0; fence_id < multi_fence.num_fences; fence_id++) {
const auto& fence = multi_fence.fences[fence_id];
@@ -278,12 +269,18 @@ void NVFlinger::Compose() {
auto nvdisp = nvdrv->GetDevice<Nvidia::Devices::nvdisp_disp0>("/dev/nvdisp_disp0");
ASSERT(nvdisp);
- nvdisp->flip(igbp_buffer.gpu_buffer_id, igbp_buffer.offset, igbp_buffer.external_format,
- igbp_buffer.width, igbp_buffer.height, igbp_buffer.stride,
- buffer->get().transform, buffer->get().crop_rect);
+ Common::Rectangle<int> crop_rect{
+ static_cast<int>(buffer.crop.Left()), static_cast<int>(buffer.crop.Top()),
+ static_cast<int>(buffer.crop.Right()), static_cast<int>(buffer.crop.Bottom())};
+
+ nvdisp->flip(igbp_buffer.BufferId(), igbp_buffer.Offset(), igbp_buffer.ExternalFormat(),
+ igbp_buffer.Width(), igbp_buffer.Height(), igbp_buffer.Stride(),
+ static_cast<android::BufferTransformFlags>(buffer.transform), crop_rect);
+
+ swap_interval = buffer.swap_interval;
- swap_interval = buffer->get().swap_interval;
- buffer_queue.ReleaseBuffer(buffer->get().slot);
+ auto fence = android::Fence::NoFence();
+ layer.GetConsumer().ReleaseBuffer(buffer, fence);
}
}
diff --git a/src/core/hle/service/nvflinger/nvflinger.h b/src/core/hle/service/nvflinger/nvflinger.h
index 7935cf773..ed160f6f9 100644
--- a/src/core/hle/service/nvflinger/nvflinger.h
+++ b/src/core/hle/service/nvflinger/nvflinger.h
@@ -1,6 +1,5 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Copyright 2021 yuzu Emulator Project
#pragma once
@@ -37,13 +36,16 @@ class Display;
class Layer;
} // namespace Service::VI
-namespace Service::NVFlinger {
+namespace Service::android {
+class BufferQueueCore;
+class BufferQueueProducer;
+} // namespace Service::android
-class BufferQueue;
+namespace Service::NVFlinger {
class NVFlinger final {
public:
- explicit NVFlinger(Core::System& system_);
+ explicit NVFlinger(Core::System& system_, HosBinderDriverServer& hos_binder_driver_server_);
~NVFlinger();
/// Sets the NVDrv module instance to use to send buffers to the GPU.
@@ -72,9 +74,6 @@ public:
/// If an invalid display ID is provided, then nullptr is returned.
[[nodiscard]] Kernel::KReadableEvent* FindVsyncEvent(u64 display_id);
- /// Obtains a buffer queue identified by the ID.
- [[nodiscard]] BufferQueue* FindBufferQueue(u32 id);
-
/// Performs a composition request to the emulated nvidia GPU and triggers the vsync events when
/// finished.
void Compose();
@@ -82,6 +81,12 @@ public:
[[nodiscard]] s64 GetNextTicks() const;
private:
+ struct Layer {
+ std::unique_ptr<android::BufferQueueCore> core;
+ std::unique_ptr<android::BufferQueueProducer> producer;
+ };
+
+private:
[[nodiscard]] std::unique_lock<std::mutex> Lock() const {
return std::unique_lock{*guard};
}
@@ -111,7 +116,6 @@ private:
std::shared_ptr<Nvidia::Module> nvdrv;
std::list<VI::Display> displays;
- std::vector<std::unique_ptr<BufferQueue>> buffer_queues;
/// Id to use for the next layer that is created, this counter is shared among all displays.
u64 next_layer_id = 1;
@@ -131,6 +135,8 @@ private:
std::jthread vsync_thread;
KernelHelpers::ServiceContext service_context;
+
+ HosBinderDriverServer& hos_binder_driver_server;
};
} // namespace Service::NVFlinger
diff --git a/src/core/hle/service/nvflinger/parcel.h b/src/core/hle/service/nvflinger/parcel.h
new file mode 100644
index 000000000..aa36e6479
--- /dev/null
+++ b/src/core/hle/service/nvflinger/parcel.h
@@ -0,0 +1,172 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Copyright 2021 yuzu Emulator Project
+
+#pragma once
+
+#include <memory>
+#include <vector>
+
+#include "common/alignment.h"
+#include "common/assert.h"
+#include "common/common_types.h"
+
+namespace Service::android {
+
+class Parcel final {
+public:
+ static constexpr std::size_t DefaultBufferSize = 0x40;
+
+ Parcel() : buffer(DefaultBufferSize) {}
+
+ template <typename T>
+ explicit Parcel(const T& out_data) : buffer(DefaultBufferSize) {
+ Write(out_data);
+ }
+
+ explicit Parcel(std::vector<u8> in_data) : buffer(std::move(in_data)) {
+ DeserializeHeader();
+ [[maybe_unused]] const std::u16string token = ReadInterfaceToken();
+ }
+
+ template <typename T>
+ void Read(T& val) {
+ static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable.");
+ ASSERT(read_index + sizeof(T) <= buffer.size());
+
+ std::memcpy(&val, buffer.data() + read_index, sizeof(T));
+ read_index += sizeof(T);
+ read_index = Common::AlignUp(read_index, 4);
+ }
+
+ template <typename T>
+ T Read() {
+ T val;
+ Read(val);
+ return val;
+ }
+
+ template <typename T>
+ void ReadFlattened(T& val) {
+ const auto flattened_size = Read<s64>();
+ ASSERT(sizeof(T) == flattened_size);
+ Read(val);
+ }
+
+ template <typename T>
+ T ReadFlattened() {
+ T val;
+ ReadFlattened(val);
+ return val;
+ }
+
+ template <typename T>
+ T ReadUnaligned() {
+ static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable.");
+ ASSERT(read_index + sizeof(T) <= buffer.size());
+
+ T val;
+ std::memcpy(&val, buffer.data() + read_index, sizeof(T));
+ read_index += sizeof(T);
+ return val;
+ }
+
+ template <typename T>
+ const std::shared_ptr<T> ReadObject() {
+ static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable.");
+
+ const auto is_valid{Read<bool>()};
+
+ if (is_valid) {
+ auto result = std::make_shared<T>();
+ ReadFlattened(*result);
+ return result;
+ }
+
+ return {};
+ }
+
+ std::u16string ReadInterfaceToken() {
+ [[maybe_unused]] const u32 unknown = Read<u32>();
+ const u32 length = Read<u32>();
+
+ std::u16string token;
+ token.reserve(length + 1);
+
+ for (u32 ch = 0; ch < length + 1; ++ch) {
+ token.push_back(ReadUnaligned<u16>());
+ }
+
+ read_index = Common::AlignUp(read_index, 4);
+
+ return token;
+ }
+
+ template <typename T>
+ void Write(const T& val) {
+ static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable.");
+
+ if (buffer.size() < write_index + sizeof(T)) {
+ buffer.resize(buffer.size() + sizeof(T) + DefaultBufferSize);
+ }
+
+ std::memcpy(buffer.data() + write_index, &val, sizeof(T));
+ write_index += sizeof(T);
+ write_index = Common::AlignUp(write_index, 4);
+ }
+
+ template <typename T>
+ void WriteObject(const T* ptr) {
+ static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable.");
+
+ if (!ptr) {
+ Write<u32>(0);
+ return;
+ }
+
+ Write<u32>(1);
+ Write<s64>(sizeof(T));
+ Write(*ptr);
+ }
+
+ template <typename T>
+ void WriteObject(const std::shared_ptr<T> ptr) {
+ WriteObject(ptr.get());
+ }
+
+ void DeserializeHeader() {
+ ASSERT(buffer.size() > sizeof(Header));
+
+ Header header{};
+ std::memcpy(&header, buffer.data(), sizeof(Header));
+
+ read_index = header.data_offset;
+ }
+
+ std::vector<u8> Serialize() const {
+ ASSERT(read_index == 0);
+
+ Header header{};
+ header.data_size = static_cast<u32>(write_index - sizeof(Header));
+ header.data_offset = sizeof(Header);
+ header.objects_size = 4;
+ header.objects_offset = static_cast<u32>(sizeof(Header) + header.data_size);
+ std::memcpy(buffer.data(), &header, sizeof(Header));
+
+ return buffer;
+ }
+
+private:
+ struct Header {
+ u32 data_size;
+ u32 data_offset;
+ u32 objects_size;
+ u32 objects_offset;
+ };
+ static_assert(sizeof(Header) == 16, "ParcelHeader has wrong size");
+
+ mutable std::vector<u8> buffer;
+ std::size_t read_index = 0;
+ std::size_t write_index = sizeof(Header);
+};
+
+} // namespace Service::android
diff --git a/src/core/hle/service/nvflinger/pixel_format.h b/src/core/hle/service/nvflinger/pixel_format.h
new file mode 100644
index 000000000..8a77d8bea
--- /dev/null
+++ b/src/core/hle/service/nvflinger/pixel_format.h
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Copyright 2021 yuzu Emulator Project
+
+#pragma once
+
+#include "common/common_types.h"
+
+namespace Service::android {
+
+enum class PixelFormat : u32 {
+ NoFormat = 0,
+ Rgba8888 = 1,
+ Rgbx8888 = 2,
+ Rgb888 = 3,
+ Rgb565 = 4,
+ Bgra8888 = 5,
+ Rgba5551 = 6,
+ Rgba4444 = 7,
+};
+
+} // namespace Service::android
diff --git a/src/core/hle/service/nvflinger/producer_listener.h b/src/core/hle/service/nvflinger/producer_listener.h
new file mode 100644
index 000000000..468e06431
--- /dev/null
+++ b/src/core/hle/service/nvflinger/producer_listener.h
@@ -0,0 +1,16 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Copyright 2021 yuzu Emulator Project
+// Copyright 2014 The Android Open Source Project
+// Parts of this implementation were base on:
+// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/IProducerListener.h
+
+#pragma once
+
+namespace Service::android {
+
+class IProducerListener {
+public:
+ virtual void OnBufferReleased() = 0;
+};
+
+} // namespace Service::android
diff --git a/src/core/hle/service/nvflinger/status.h b/src/core/hle/service/nvflinger/status.h
new file mode 100644
index 000000000..a003eda89
--- /dev/null
+++ b/src/core/hle/service/nvflinger/status.h
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Copyright 2021 yuzu Emulator Project
+
+#pragma once
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+
+namespace Service::android {
+
+enum class Status : s32 {
+ None = 0,
+ NoError = 0,
+ StaleBufferSlot = 1,
+ NoBufferAvailable = 2,
+ PresentLater = 3,
+ WouldBlock = -11,
+ NoMemory = -12,
+ Busy = -16,
+ NoInit = -19,
+ BadValue = -22,
+ InvalidOperation = -37,
+ BufferNeedsReallocation = 1,
+ ReleaseAllBuffers = 2,
+};
+DECLARE_ENUM_FLAG_OPERATORS(Status);
+
+} // namespace Service::android
diff --git a/src/core/hle/service/nvflinger/ui/fence.h b/src/core/hle/service/nvflinger/ui/fence.h
new file mode 100644
index 000000000..4a74e26a3
--- /dev/null
+++ b/src/core/hle/service/nvflinger/ui/fence.h
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Copyright 2021 yuzu Emulator Project
+// Copyright 2012 The Android Open Source Project
+// Parts of this implementation were base on:
+// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/ui/Fence.h
+
+#pragma once
+
+#include <array>
+
+#include "common/common_types.h"
+#include "core/hle/service/nvdrv/nvdata.h"
+
+namespace Service::android {
+
+class Fence {
+public:
+ constexpr Fence() = default;
+
+ static constexpr Fence NoFence() {
+ Fence fence;
+ fence.fences[0].id = -1;
+ return fence;
+ }
+
+public:
+ u32 num_fences{};
+ std::array<Service::Nvidia::NvFence, 4> fences{};
+};
+static_assert(sizeof(Fence) == 36, "Fence has wrong size");
+
+} // namespace Service::android
diff --git a/src/core/hle/service/nvflinger/ui/graphic_buffer.h b/src/core/hle/service/nvflinger/ui/graphic_buffer.h
new file mode 100644
index 000000000..7abbf78ba
--- /dev/null
+++ b/src/core/hle/service/nvflinger/ui/graphic_buffer.h
@@ -0,0 +1,100 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Copyright 2021 yuzu Emulator Project
+// Copyright 2007 The Android Open Source Project
+// Parts of this implementation were base on:
+// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/ui/GraphicBuffer.h
+
+#pragma once
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "core/hle/service/nvflinger/pixel_format.h"
+
+namespace Service::android {
+
+class GraphicBuffer final {
+public:
+ constexpr GraphicBuffer() = default;
+
+ constexpr GraphicBuffer(u32 width_, u32 height_, PixelFormat format_, u32 usage_)
+ : width{static_cast<s32>(width_)}, height{static_cast<s32>(height_)}, format{format_},
+ usage{static_cast<s32>(usage_)} {}
+
+ constexpr u32 Width() const {
+ return static_cast<u32>(width);
+ }
+
+ constexpr u32 Height() const {
+ return static_cast<u32>(height);
+ }
+
+ constexpr u32 Stride() const {
+ return static_cast<u32>(stride);
+ }
+
+ constexpr u32 Usage() const {
+ return static_cast<u32>(usage);
+ }
+
+ constexpr PixelFormat Format() const {
+ return format;
+ }
+
+ constexpr u32 BufferId() const {
+ return buffer_id;
+ }
+
+ constexpr PixelFormat ExternalFormat() const {
+ return external_format;
+ }
+
+ constexpr u32 Handle() const {
+ return handle;
+ }
+
+ constexpr u32 Offset() const {
+ return offset;
+ }
+
+ constexpr bool NeedsReallocation(u32 width_, u32 height_, PixelFormat format_,
+ u32 usage_) const {
+ if (static_cast<s32>(width_) != width) {
+ return true;
+ }
+
+ if (static_cast<s32>(height_) != height) {
+ return true;
+ }
+
+ if (format_ != format) {
+ return true;
+ }
+
+ if ((static_cast<u32>(usage) & usage_) != usage_) {
+ return true;
+ }
+
+ return false;
+ }
+
+private:
+ u32 magic{};
+ s32 width{};
+ s32 height{};
+ s32 stride{};
+ PixelFormat format{};
+ s32 usage{};
+ INSERT_PADDING_WORDS(1);
+ u32 index{};
+ INSERT_PADDING_WORDS(3);
+ u32 buffer_id{};
+ INSERT_PADDING_WORDS(6);
+ PixelFormat external_format{};
+ INSERT_PADDING_WORDS(10);
+ u32 handle{};
+ u32 offset{};
+ INSERT_PADDING_WORDS(60);
+};
+static_assert(sizeof(GraphicBuffer) == 0x16C, "GraphicBuffer has wrong size");
+
+} // namespace Service::android
diff --git a/src/core/hle/service/nvflinger/window.h b/src/core/hle/service/nvflinger/window.h
new file mode 100644
index 000000000..e26f8160e
--- /dev/null
+++ b/src/core/hle/service/nvflinger/window.h
@@ -0,0 +1,53 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Copyright 2021 yuzu Emulator Project
+
+#pragma once
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+
+namespace Service::android {
+
+/// Attributes queryable with Query
+enum class NativeWindow : s32 {
+ Width = 0,
+ Height = 1,
+ Format = 2,
+ MinUndequeedBuffers = 3,
+ QueuesToWindowComposer = 4,
+ ConcreteType = 5,
+ DefaultWidth = 6,
+ DefaultHeight = 7,
+ TransformHint = 8,
+ ConsumerRunningBehind = 9,
+ ConsumerUsageBits = 10,
+ StickyTransform = 11,
+ DefaultDataSpace = 12,
+ BufferAge = 13,
+};
+
+/// Parameter for Connect/Disconnect
+enum class NativeWindowApi : s32 {
+ NoConnectedApi = 0,
+ Egl = 1,
+ Cpu = 2,
+ Media = 3,
+ Camera = 4,
+};
+
+/// Scaling mode parameter for QueueBuffer
+enum class NativeWindowScalingMode : s32 {
+ Freeze = 0,
+ ScaleToWindow = 1,
+ ScaleCrop = 2,
+ NoScaleCrop = 3,
+};
+
+/// Transform parameter for QueueBuffer
+enum class NativeWindowTransform : u32 {
+ None = 0x0,
+ InverseDisplay = 0x08,
+};
+DECLARE_ENUM_FLAG_OPERATORS(NativeWindowTransform);
+
+} // namespace Service::android
diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp
index eb1138313..0f59a03c5 100644
--- a/src/core/hle/service/service.cpp
+++ b/src/core/hle/service/service.cpp
@@ -32,6 +32,7 @@
#include "core/hle/service/glue/glue.h"
#include "core/hle/service/grc/grc.h"
#include "core/hle/service/hid/hid.h"
+#include "core/hle/service/jit/jit.h"
#include "core/hle/service/lbl/lbl.h"
#include "core/hle/service/ldn/ldn.h"
#include "core/hle/service/ldr/ldr.h"
@@ -49,6 +50,7 @@
#include "core/hle/service/npns/npns.h"
#include "core/hle/service/ns/ns.h"
#include "core/hle/service/nvdrv/nvdrv.h"
+#include "core/hle/service/nvflinger/hos_binder_driver_server.h"
#include "core/hle/service/nvflinger/nvflinger.h"
#include "core/hle/service/olsc/olsc.h"
#include "core/hle/service/pcie/pcie.h"
@@ -90,8 +92,9 @@ namespace Service {
}
ServiceFrameworkBase::ServiceFrameworkBase(Core::System& system_, const char* service_name_,
- u32 max_sessions_, InvokerFn* handler_invoker_)
- : SessionRequestHandler(system_.Kernel(), service_name_), system{system_},
+ ServiceThreadType thread_type, u32 max_sessions_,
+ InvokerFn* handler_invoker_)
+ : SessionRequestHandler(system_.Kernel(), service_name_, thread_type), system{system_},
service_name{service_name_}, max_sessions{max_sessions_}, handler_invoker{handler_invoker_} {}
ServiceFrameworkBase::~ServiceFrameworkBase() {
@@ -230,7 +233,8 @@ ResultCode ServiceFrameworkBase::HandleSyncRequest(Kernel::KServerSession& sessi
/// Initialize Services
Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system)
- : nv_flinger{std::make_unique<NVFlinger::NVFlinger>(system)} {
+ : hos_binder_driver_server{std::make_unique<NVFlinger::HosBinderDriverServer>(system)},
+ nv_flinger{std::make_unique<NVFlinger::NVFlinger>(system, *hos_binder_driver_server)} {
// 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.
@@ -259,6 +263,7 @@ Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system
Glue::InstallInterfaces(system);
GRC::InstallInterfaces(*sm, system);
HID::InstallInterfaces(*sm, system);
+ JIT::InstallInterfaces(*sm, system);
LBL::InstallInterfaces(*sm, system);
LDN::InstallInterfaces(*sm, system);
LDR::InstallInterfaces(*sm, system);
@@ -290,7 +295,7 @@ Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system
SSL::InstallInterfaces(*sm, system);
Time::InstallInterfaces(system);
USB::InstallInterfaces(*sm, system);
- VI::InstallInterfaces(*sm, system, *nv_flinger);
+ VI::InstallInterfaces(*sm, system, *nv_flinger, *hos_binder_driver_server);
WLAN::InstallInterfaces(*sm, system);
}
diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h
index c9d6b879d..148265218 100644
--- a/src/core/hle/service/service.h
+++ b/src/core/hle/service/service.h
@@ -9,7 +9,6 @@
#include <string>
#include <boost/container/flat_map.hpp>
#include "common/common_types.h"
-#include "common/spin_lock.h"
#include "core/hle/kernel/hle_ipc.h"
////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -33,8 +32,9 @@ class FileSystemController;
}
namespace NVFlinger {
+class HosBinderDriverServer;
class NVFlinger;
-}
+} // namespace NVFlinger
namespace SM {
class ServiceManager;
@@ -89,7 +89,7 @@ protected:
using HandlerFnP = void (Self::*)(Kernel::HLERequestContext&);
/// Used to gain exclusive access to the service members, e.g. from CoreTiming thread.
- [[nodiscard]] std::scoped_lock<Common::SpinLock> LockService() {
+ [[nodiscard]] std::scoped_lock<std::mutex> LockService() {
return std::scoped_lock{lock_service};
}
@@ -113,7 +113,8 @@ private:
Kernel::HLERequestContext& ctx);
explicit ServiceFrameworkBase(Core::System& system_, const char* service_name_,
- u32 max_sessions_, InvokerFn* handler_invoker_);
+ ServiceThreadType thread_type, u32 max_sessions_,
+ InvokerFn* handler_invoker_);
~ServiceFrameworkBase() override;
void RegisterHandlersBase(const FunctionInfoBase* functions, std::size_t n);
@@ -133,7 +134,7 @@ private:
boost::container::flat_map<u32, FunctionInfoBase> handlers_tipc;
/// Used to gain exclusive access to the service members, e.g. from CoreTiming thread.
- Common::SpinLock lock_service;
+ std::mutex lock_service;
};
/**
@@ -175,14 +176,17 @@ protected:
/**
* Initializes the handler with no functions installed.
*
- * @param system_ The system context to construct this service under.
+ * @param system_ The system context to construct this service under.
* @param service_name_ Name of the service.
- * @param max_sessions_ Maximum number of sessions that can be
- * connected to this service at the same time.
+ * @param thread_type Specifies the thread type for this service. If this is set to CreateNew,
+ * it creates a new thread for it, otherwise this uses the default thread.
+ * @param max_sessions_ Maximum number of sessions that can be connected to this service at the
+ * same time.
*/
explicit ServiceFramework(Core::System& system_, const char* service_name_,
+ ServiceThreadType thread_type = ServiceThreadType::Default,
u32 max_sessions_ = ServerSessionCountMax)
- : ServiceFrameworkBase(system_, service_name_, max_sessions_, Invoker) {}
+ : ServiceFrameworkBase(system_, service_name_, thread_type, max_sessions_, Invoker) {}
/// Registers handlers in the service.
template <std::size_t N>
@@ -236,6 +240,7 @@ public:
~Services();
private:
+ std::unique_ptr<NVFlinger::HosBinderDriverServer> hos_binder_driver_server;
std::unique_ptr<NVFlinger::NVFlinger> nv_flinger;
};
diff --git a/src/core/hle/service/sm/sm.cpp b/src/core/hle/service/sm/sm.cpp
index 695a1faa6..13f5e08ec 100644
--- a/src/core/hle/service/sm/sm.cpp
+++ b/src/core/hle/service/sm/sm.cpp
@@ -153,7 +153,7 @@ ResultVal<Kernel::KClientSession*> SM::GetServiceImpl(Kernel::HLERequestContext&
auto& port = port_result.Unwrap();
SCOPE_EXIT({ port->GetClientPort().Close(); });
- server_ports.emplace_back(&port->GetServerPort());
+ kernel.RegisterServerObject(&port->GetServerPort());
// Create a new session.
Kernel::KClientSession* session{};
@@ -206,7 +206,7 @@ void SM::UnregisterService(Kernel::HLERequestContext& ctx) {
}
SM::SM(ServiceManager& service_manager_, Core::System& system_)
- : ServiceFramework{system_, "sm:", 4},
+ : ServiceFramework{system_, "sm:", ServiceThreadType::Default, 4},
service_manager{service_manager_}, kernel{system_.Kernel()} {
RegisterHandlers({
{0, &SM::Initialize, "Initialize"},
@@ -224,10 +224,6 @@ SM::SM(ServiceManager& service_manager_, Core::System& system_)
});
}
-SM::~SM() {
- for (auto& server_port : server_ports) {
- server_port->Close();
- }
-}
+SM::~SM() = default;
} // namespace Service::SM
diff --git a/src/core/hle/service/sm/sm.h b/src/core/hle/service/sm/sm.h
index 021eb51b4..f3ff7b27e 100644
--- a/src/core/hle/service/sm/sm.h
+++ b/src/core/hle/service/sm/sm.h
@@ -22,7 +22,6 @@ class KClientPort;
class KClientSession;
class KernelCore;
class KPort;
-class KServerPort;
class SessionRequestHandler;
} // namespace Kernel
@@ -48,7 +47,6 @@ private:
ServiceManager& service_manager;
bool is_initialized{};
Kernel::KernelCore& kernel;
- std::vector<Kernel::KServerPort*> server_ports;
};
class ServiceManager {
diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp
index fc93fb743..d25b050e2 100644
--- a/src/core/hle/service/sockets/bsd.cpp
+++ b/src/core/hle/service/sockets/bsd.cpp
@@ -689,6 +689,9 @@ Errno BSD::SetSockOptImpl(s32 fd, u32 level, OptName optname, size_t optlen, con
case OptName::REUSEADDR:
ASSERT(value == 0 || value == 1);
return Translate(socket->SetReuseAddr(value != 0));
+ case OptName::KEEPALIVE:
+ ASSERT(value == 0 || value == 1);
+ return Translate(socket->SetKeepAlive(value != 0));
case OptName::BROADCAST:
ASSERT(value == 0 || value == 1);
return Translate(socket->SetBroadcast(value != 0));
@@ -837,7 +840,8 @@ void BSD::BuildErrnoResponse(Kernel::HLERequestContext& ctx, Errno bsd_errno) co
rb.PushEnum(bsd_errno);
}
-BSD::BSD(Core::System& system_, const char* name) : ServiceFramework{system_, name} {
+BSD::BSD(Core::System& system_, const char* name)
+ : ServiceFramework{system_, name, ServiceThreadType::CreateNew} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &BSD::RegisterClient, "RegisterClient"},
diff --git a/src/core/hle/service/sockets/sfdnsres.cpp b/src/core/hle/service/sockets/sfdnsres.cpp
index fb6142c49..a193fb578 100644
--- a/src/core/hle/service/sockets/sfdnsres.cpp
+++ b/src/core/hle/service/sockets/sfdnsres.cpp
@@ -2,8 +2,28 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include "common/string_util.h"
+#include "common/swap.h"
+#include "core/core.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/service/sockets/sfdnsres.h"
+#include "core/memory.h"
+
+#ifdef _WIN32
+#include <ws2tcpip.h>
+#elif YUZU_UNIX
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#ifndef EAI_NODATA
+#define EAI_NODATA EAI_NONAME
+#endif
+#endif
namespace Service::Sockets {
@@ -21,7 +41,7 @@ SFDNSRES::SFDNSRES(Core::System& system_) : ServiceFramework{system_, "sfdnsres"
{9, nullptr, "CancelRequest"},
{10, nullptr, "GetHostByNameRequestWithOptions"},
{11, nullptr, "GetHostByAddrRequestWithOptions"},
- {12, nullptr, "GetAddrInfoRequestWithOptions"},
+ {12, &SFDNSRES::GetAddrInfoRequestWithOptions, "GetAddrInfoRequestWithOptions"},
{13, nullptr, "GetNameInfoRequestWithOptions"},
{14, nullptr, "ResolverSetOptionRequest"},
{15, nullptr, "ResolverGetOptionRequest"},
@@ -31,7 +51,142 @@ SFDNSRES::SFDNSRES(Core::System& system_) : ServiceFramework{system_, "sfdnsres"
SFDNSRES::~SFDNSRES() = default;
-void SFDNSRES::GetAddrInfoRequest(Kernel::HLERequestContext& ctx) {
+enum class NetDbError : s32 {
+ Internal = -1,
+ Success = 0,
+ HostNotFound = 1,
+ TryAgain = 2,
+ NoRecovery = 3,
+ NoData = 4,
+};
+
+static NetDbError AddrInfoErrorToNetDbError(s32 result) {
+ // Best effort guess to map errors
+ switch (result) {
+ case 0:
+ return NetDbError::Success;
+ case EAI_AGAIN:
+ return NetDbError::TryAgain;
+ case EAI_NODATA:
+ return NetDbError::NoData;
+ default:
+ return NetDbError::HostNotFound;
+ }
+}
+
+static std::vector<u8> SerializeAddrInfo(const addrinfo* addrinfo, s32 result_code,
+ std::string_view host) {
+ // Adapted from
+ // https://github.com/switchbrew/libnx/blob/c5a9a909a91657a9818a3b7e18c9b91ff0cbb6e3/nx/source/runtime/resolver.c#L190
+ std::vector<u8> data;
+
+ auto* current = addrinfo;
+ while (current != nullptr) {
+ struct SerializedResponseHeader {
+ u32 magic;
+ s32 flags;
+ s32 family;
+ s32 socket_type;
+ s32 protocol;
+ u32 address_length;
+ };
+ static_assert(sizeof(SerializedResponseHeader) == 0x18,
+ "Response header size must be 0x18 bytes");
+
+ constexpr auto header_size = sizeof(SerializedResponseHeader);
+ const auto addr_size =
+ current->ai_addr && current->ai_addrlen > 0 ? current->ai_addrlen : 4;
+ const auto canonname_size = current->ai_canonname ? strlen(current->ai_canonname) + 1 : 1;
+
+ const auto last_size = data.size();
+ data.resize(last_size + header_size + addr_size + canonname_size);
+
+ // Header in network byte order
+ SerializedResponseHeader header{};
+
+ constexpr auto HEADER_MAGIC = 0xBEEFCAFE;
+ header.magic = htonl(HEADER_MAGIC);
+ header.family = htonl(current->ai_family);
+ header.flags = htonl(current->ai_flags);
+ header.socket_type = htonl(current->ai_socktype);
+ header.protocol = htonl(current->ai_protocol);
+ header.address_length = current->ai_addr ? htonl((u32)current->ai_addrlen) : 0;
+
+ auto* header_ptr = data.data() + last_size;
+ std::memcpy(header_ptr, &header, header_size);
+
+ if (header.address_length == 0) {
+ std::memset(header_ptr + header_size, 0, 4);
+ } else {
+ switch (current->ai_family) {
+ case AF_INET: {
+ struct SockAddrIn {
+ s16 sin_family;
+ u16 sin_port;
+ u32 sin_addr;
+ u8 sin_zero[8];
+ };
+
+ SockAddrIn serialized_addr{};
+ const auto addr = *reinterpret_cast<sockaddr_in*>(current->ai_addr);
+ serialized_addr.sin_port = htons(addr.sin_port);
+ serialized_addr.sin_family = htons(addr.sin_family);
+ serialized_addr.sin_addr = htonl(addr.sin_addr.s_addr);
+ std::memcpy(header_ptr + header_size, &serialized_addr, sizeof(SockAddrIn));
+
+ char addr_string_buf[64]{};
+ inet_ntop(AF_INET, &addr.sin_addr, addr_string_buf, std::size(addr_string_buf));
+ LOG_INFO(Service, "Resolved host '{}' to IPv4 address {}", host, addr_string_buf);
+ break;
+ }
+ case AF_INET6: {
+ struct SockAddrIn6 {
+ s16 sin6_family;
+ u16 sin6_port;
+ u32 sin6_flowinfo;
+ u8 sin6_addr[16];
+ u32 sin6_scope_id;
+ };
+
+ SockAddrIn6 serialized_addr{};
+ const auto addr = *reinterpret_cast<sockaddr_in6*>(current->ai_addr);
+ serialized_addr.sin6_family = htons(addr.sin6_family);
+ serialized_addr.sin6_port = htons(addr.sin6_port);
+ serialized_addr.sin6_flowinfo = htonl(addr.sin6_flowinfo);
+ serialized_addr.sin6_scope_id = htonl(addr.sin6_scope_id);
+ std::memcpy(serialized_addr.sin6_addr, &addr.sin6_addr,
+ sizeof(SockAddrIn6::sin6_addr));
+ std::memcpy(header_ptr + header_size, &serialized_addr, sizeof(SockAddrIn6));
+
+ char addr_string_buf[64]{};
+ inet_ntop(AF_INET6, &addr.sin6_addr, addr_string_buf, std::size(addr_string_buf));
+ LOG_INFO(Service, "Resolved host '{}' to IPv6 address {}", host, addr_string_buf);
+ break;
+ }
+ default:
+ std::memcpy(header_ptr + header_size, current->ai_addr, addr_size);
+ break;
+ }
+ }
+ if (current->ai_canonname) {
+ std::memcpy(header_ptr + addr_size, current->ai_canonname, canonname_size);
+ } else {
+ *(header_ptr + header_size + addr_size) = 0;
+ }
+
+ current = current->ai_next;
+ }
+
+ // 4-byte sentinel value
+ data.push_back(0);
+ data.push_back(0);
+ data.push_back(0);
+ data.push_back(0);
+
+ return data;
+}
+
+static std::pair<u32, s32> GetAddrInfoRequestImpl(Kernel::HLERequestContext& ctx) {
struct Parameters {
u8 use_nsd_resolve;
u32 unknown;
@@ -42,11 +197,51 @@ void SFDNSRES::GetAddrInfoRequest(Kernel::HLERequestContext& ctx) {
const auto parameters = rp.PopRaw<Parameters>();
LOG_WARNING(Service,
- "(STUBBED) called. use_nsd_resolve={}, unknown=0x{:08X}, process_id=0x{:016X}",
+ "called with ignored parameters: use_nsd_resolve={}, unknown={}, process_id={}",
parameters.use_nsd_resolve, parameters.unknown, parameters.process_id);
- IPC::ResponseBuilder rb{ctx, 2};
+ const auto host_buffer = ctx.ReadBuffer(0);
+ const std::string host = Common::StringFromBuffer(host_buffer);
+
+ const auto service_buffer = ctx.ReadBuffer(1);
+ const std::string service = Common::StringFromBuffer(service_buffer);
+
+ addrinfo* addrinfo;
+ // Pass null for hints. Serialized hints are also passed in a buffer, but are ignored for now
+ s32 result_code = getaddrinfo(host.c_str(), service.c_str(), nullptr, &addrinfo);
+
+ u32 data_size = 0;
+ if (result_code == 0 && addrinfo != nullptr) {
+ const std::vector<u8>& data = SerializeAddrInfo(addrinfo, result_code, host);
+ data_size = static_cast<u32>(data.size());
+ freeaddrinfo(addrinfo);
+
+ ctx.WriteBuffer(data, 0);
+ }
+
+ return std::make_pair(data_size, result_code);
+}
+
+void SFDNSRES::GetAddrInfoRequest(Kernel::HLERequestContext& ctx) {
+ auto [data_size, result_code] = GetAddrInfoRequestImpl(ctx);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(ResultSuccess);
+ rb.Push(static_cast<s32>(AddrInfoErrorToNetDbError(result_code))); // NetDBErrorCode
+ rb.Push(result_code); // errno
+ rb.Push(data_size); // serialized size
+}
+
+void SFDNSRES::GetAddrInfoRequestWithOptions(Kernel::HLERequestContext& ctx) {
+ // Additional options are ignored
+ auto [data_size, result_code] = GetAddrInfoRequestImpl(ctx);
+
+ IPC::ResponseBuilder rb{ctx, 5};
rb.Push(ResultSuccess);
+ rb.Push(data_size); // serialized size
+ rb.Push(result_code); // errno
+ rb.Push(static_cast<s32>(AddrInfoErrorToNetDbError(result_code))); // NetDBErrorCode
+ rb.Push(0);
}
-} // namespace Service::Sockets
+} // namespace Service::Sockets \ No newline at end of file
diff --git a/src/core/hle/service/sockets/sfdnsres.h b/src/core/hle/service/sockets/sfdnsres.h
index 5d3b4dc2d..f0c57377d 100644
--- a/src/core/hle/service/sockets/sfdnsres.h
+++ b/src/core/hle/service/sockets/sfdnsres.h
@@ -19,6 +19,7 @@ public:
private:
void GetAddrInfoRequest(Kernel::HLERequestContext& ctx);
+ void GetAddrInfoRequestWithOptions(Kernel::HLERequestContext& ctx);
};
} // namespace Service::Sockets
diff --git a/src/core/hle/service/sockets/sockets.h b/src/core/hle/service/sockets/sockets.h
index 02dbbae40..d69c86431 100644
--- a/src/core/hle/service/sockets/sockets.h
+++ b/src/core/hle/service/sockets/sockets.h
@@ -46,6 +46,7 @@ enum class Protocol : u32 {
enum class OptName : u32 {
REUSEADDR = 0x4,
+ KEEPALIVE = 0x8,
BROADCAST = 0x20,
LINGER = 0x80,
SNDBUF = 0x1001,
diff --git a/src/core/hle/service/vi/display/vi_display.cpp b/src/core/hle/service/vi/display/vi_display.cpp
index b7705c02a..558022511 100644
--- a/src/core/hle/service/vi/display/vi_display.cpp
+++ b/src/core/hle/service/vi/display/vi_display.cpp
@@ -13,14 +13,34 @@
#include "core/hle/kernel/k_readable_event.h"
#include "core/hle/kernel/k_writable_event.h"
#include "core/hle/service/kernel_helpers.h"
+#include "core/hle/service/nvflinger/buffer_item_consumer.h"
+#include "core/hle/service/nvflinger/buffer_queue_consumer.h"
+#include "core/hle/service/nvflinger/buffer_queue_core.h"
+#include "core/hle/service/nvflinger/buffer_queue_producer.h"
+#include "core/hle/service/nvflinger/hos_binder_driver_server.h"
#include "core/hle/service/vi/display/vi_display.h"
#include "core/hle/service/vi/layer/vi_layer.h"
namespace Service::VI {
-Display::Display(u64 id, std::string name_, KernelHelpers::ServiceContext& service_context_,
- Core::System& system_)
- : display_id{id}, name{std::move(name_)}, service_context{service_context_} {
+struct BufferQueue {
+ std::shared_ptr<android::BufferQueueCore> core;
+ std::unique_ptr<android::BufferQueueProducer> producer;
+ std::unique_ptr<android::BufferQueueConsumer> consumer;
+};
+
+static BufferQueue CreateBufferQueue(KernelHelpers::ServiceContext& service_context) {
+ auto buffer_queue_core = std::make_shared<android::BufferQueueCore>();
+ return {buffer_queue_core,
+ std::make_unique<android::BufferQueueProducer>(service_context, buffer_queue_core),
+ std::make_unique<android::BufferQueueConsumer>(buffer_queue_core)};
+}
+
+Display::Display(u64 id, std::string name_,
+ NVFlinger::HosBinderDriverServer& hos_binder_driver_server_,
+ KernelHelpers::ServiceContext& service_context_, Core::System& system_)
+ : display_id{id}, name{std::move(name_)}, hos_binder_driver_server{hos_binder_driver_server_},
+ service_context{service_context_} {
vsync_event = service_context.CreateEvent(fmt::format("Display VSync Event {}", id));
}
@@ -44,21 +64,29 @@ void Display::SignalVSyncEvent() {
vsync_event->GetWritableEvent().Signal();
}
-void Display::CreateLayer(u64 layer_id, NVFlinger::BufferQueue& buffer_queue) {
- // TODO(Subv): Support more than 1 layer.
+void Display::CreateLayer(u64 layer_id, u32 binder_id) {
ASSERT_MSG(layers.empty(), "Only one layer is supported per display at the moment");
- layers.emplace_back(std::make_shared<Layer>(layer_id, buffer_queue));
+ auto [core, producer, consumer] = CreateBufferQueue(service_context);
+
+ auto buffer_item_consumer = std::make_shared<android::BufferItemConsumer>(std::move(consumer));
+ buffer_item_consumer->Connect(false);
+
+ layers.emplace_back(std::make_unique<Layer>(layer_id, binder_id, *core, *producer,
+ std::move(buffer_item_consumer)));
+
+ hos_binder_driver_server.RegisterProducer(std::move(producer));
}
void Display::CloseLayer(u64 layer_id) {
- std::erase_if(layers, [layer_id](const auto& layer) { return layer->GetID() == layer_id; });
+ std::erase_if(layers,
+ [layer_id](const auto& layer) { return layer->GetLayerId() == layer_id; });
}
Layer* Display::FindLayer(u64 layer_id) {
const auto itr =
- std::find_if(layers.begin(), layers.end(), [layer_id](const std::shared_ptr<Layer>& layer) {
- return layer->GetID() == layer_id;
+ std::find_if(layers.begin(), layers.end(), [layer_id](const std::unique_ptr<Layer>& layer) {
+ return layer->GetLayerId() == layer_id;
});
if (itr == layers.end()) {
@@ -70,8 +98,8 @@ Layer* Display::FindLayer(u64 layer_id) {
const Layer* Display::FindLayer(u64 layer_id) const {
const auto itr =
- std::find_if(layers.begin(), layers.end(), [layer_id](const std::shared_ptr<Layer>& layer) {
- return layer->GetID() == layer_id;
+ std::find_if(layers.begin(), layers.end(), [layer_id](const std::unique_ptr<Layer>& layer) {
+ return layer->GetLayerId() == layer_id;
});
if (itr == layers.end()) {
diff --git a/src/core/hle/service/vi/display/vi_display.h b/src/core/hle/service/vi/display/vi_display.h
index 329f4ba86..e93d084ee 100644
--- a/src/core/hle/service/vi/display/vi_display.h
+++ b/src/core/hle/service/vi/display/vi_display.h
@@ -15,12 +15,17 @@ namespace Kernel {
class KEvent;
}
-namespace Service::NVFlinger {
-class BufferQueue;
+namespace Service::android {
+class BufferQueueProducer;
}
+
namespace Service::KernelHelpers {
class ServiceContext;
-} // namespace Service::KernelHelpers
+}
+
+namespace Service::NVFlinger {
+class HosBinderDriverServer;
+}
namespace Service::VI {
@@ -35,12 +40,13 @@ public:
/// Constructs a display with a given unique ID and name.
///
/// @param id The unique ID for this display.
+ /// @param hos_binder_driver_server_ NVFlinger HOSBinderDriver server instance.
/// @param service_context_ The ServiceContext for the owning service.
/// @param name_ The name for this display.
/// @param system_ The global system instance.
///
- Display(u64 id, std::string name_, KernelHelpers::ServiceContext& service_context_,
- Core::System& system_);
+ Display(u64 id, std::string name_, NVFlinger::HosBinderDriverServer& hos_binder_driver_server_,
+ KernelHelpers::ServiceContext& service_context_, Core::System& system_);
~Display();
/// Gets the unique ID assigned to this display.
@@ -64,6 +70,10 @@ public:
/// Gets a layer for this display based off an index.
const Layer& GetLayer(std::size_t index) const;
+ std::size_t GetNumLayers() const {
+ return layers.size();
+ }
+
/// Gets the readable vsync event.
Kernel::KReadableEvent& GetVSyncEvent();
@@ -72,10 +82,10 @@ public:
/// Creates and adds a layer to this display with the given ID.
///
- /// @param layer_id The ID to assign to the created layer.
- /// @param buffer_queue The buffer queue for the layer instance to use.
+ /// @param layer_id The ID to assign to the created layer.
+ /// @param binder_id The ID assigned to the buffer queue.
///
- void CreateLayer(u64 layer_id, NVFlinger::BufferQueue& buffer_queue);
+ void CreateLayer(u64 layer_id, u32 binder_id);
/// Closes and removes a layer from this display with the given ID.
///
@@ -104,9 +114,10 @@ public:
private:
u64 display_id;
std::string name;
+ NVFlinger::HosBinderDriverServer& hos_binder_driver_server;
KernelHelpers::ServiceContext& service_context;
- std::vector<std::shared_ptr<Layer>> layers;
+ std::vector<std::unique_ptr<Layer>> layers;
Kernel::KEvent* vsync_event{};
};
diff --git a/src/core/hle/service/vi/layer/vi_layer.cpp b/src/core/hle/service/vi/layer/vi_layer.cpp
index 9bc382587..93858e91f 100644
--- a/src/core/hle/service/vi/layer/vi_layer.cpp
+++ b/src/core/hle/service/vi/layer/vi_layer.cpp
@@ -6,7 +6,11 @@
namespace Service::VI {
-Layer::Layer(u64 id, NVFlinger::BufferQueue& queue) : layer_id{id}, buffer_queue{queue} {}
+Layer::Layer(u64 layer_id_, u32 binder_id_, android::BufferQueueCore& core_,
+ android::BufferQueueProducer& binder_,
+ std::shared_ptr<android::BufferItemConsumer>&& consumer_)
+ : layer_id{layer_id_}, binder_id{binder_id_}, core{core_}, binder{binder_}, consumer{std::move(
+ consumer_)} {}
Layer::~Layer() = default;
diff --git a/src/core/hle/service/vi/layer/vi_layer.h b/src/core/hle/service/vi/layer/vi_layer.h
index ebdd85505..c28b14450 100644
--- a/src/core/hle/service/vi/layer/vi_layer.h
+++ b/src/core/hle/service/vi/layer/vi_layer.h
@@ -4,11 +4,15 @@
#pragma once
+#include <memory>
+
#include "common/common_types.h"
-namespace Service::NVFlinger {
-class BufferQueue;
-}
+namespace Service::android {
+class BufferItemConsumer;
+class BufferQueueCore;
+class BufferQueueProducer;
+} // namespace Service::android
namespace Service::VI {
@@ -17,10 +21,13 @@ class Layer {
public:
/// Constructs a layer with a given ID and buffer queue.
///
- /// @param id The ID to assign to this layer.
- /// @param queue The buffer queue for this layer to use.
+ /// @param layer_id_ The ID to assign to this layer.
+ /// @param binder_id_ The binder ID to assign to this layer.
+ /// @param binder_ The buffer producer queue for this layer to use.
///
- Layer(u64 id, NVFlinger::BufferQueue& queue);
+ Layer(u64 layer_id_, u32 binder_id_, android::BufferQueueCore& core_,
+ android::BufferQueueProducer& binder_,
+ std::shared_ptr<android::BufferItemConsumer>&& consumer_);
~Layer();
Layer(const Layer&) = delete;
@@ -30,23 +37,47 @@ public:
Layer& operator=(Layer&&) = delete;
/// Gets the ID for this layer.
- u64 GetID() const {
+ u64 GetLayerId() const {
return layer_id;
}
+ /// Gets the binder ID for this layer.
+ u32 GetBinderId() const {
+ return binder_id;
+ }
+
/// Gets a reference to the buffer queue this layer is using.
- NVFlinger::BufferQueue& GetBufferQueue() {
- return buffer_queue;
+ android::BufferQueueProducer& GetBufferQueue() {
+ return binder;
}
/// Gets a const reference to the buffer queue this layer is using.
- const NVFlinger::BufferQueue& GetBufferQueue() const {
- return buffer_queue;
+ const android::BufferQueueProducer& GetBufferQueue() const {
+ return binder;
+ }
+
+ android::BufferItemConsumer& GetConsumer() {
+ return *consumer;
+ }
+
+ const android::BufferItemConsumer& GetConsumer() const {
+ return *consumer;
+ }
+
+ android::BufferQueueCore& Core() {
+ return core;
+ }
+
+ const android::BufferQueueCore& Core() const {
+ return core;
}
private:
- u64 layer_id;
- NVFlinger::BufferQueue& buffer_queue;
+ const u64 layer_id;
+ const u32 binder_id;
+ android::BufferQueueCore& core;
+ android::BufferQueueProducer& binder;
+ std::shared_ptr<android::BufferItemConsumer> consumer;
};
} // namespace Service::VI
diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp
index 75ee3e5e4..a3436c8ea 100644
--- a/src/core/hle/service/vi/vi.cpp
+++ b/src/core/hle/service/vi/vi.cpp
@@ -22,8 +22,11 @@
#include "core/hle/kernel/k_readable_event.h"
#include "core/hle/kernel/k_thread.h"
#include "core/hle/service/nvdrv/nvdata.h"
-#include "core/hle/service/nvflinger/buffer_queue.h"
+#include "core/hle/service/nvflinger/binder.h"
+#include "core/hle/service/nvflinger/buffer_queue_producer.h"
+#include "core/hle/service/nvflinger/hos_binder_driver_server.h"
#include "core/hle/service/nvflinger/nvflinger.h"
+#include "core/hle/service/nvflinger/parcel.h"
#include "core/hle/service/service.h"
#include "core/hle/service/vi/vi.h"
#include "core/hle/service/vi/vi_m.h"
@@ -57,447 +60,25 @@ struct DisplayInfo {
};
static_assert(sizeof(DisplayInfo) == 0x60, "DisplayInfo has wrong size");
-class Parcel {
+class NativeWindow final {
public:
- // This default size was chosen arbitrarily.
- static constexpr std::size_t DefaultBufferSize = 0x40;
- Parcel() : buffer(DefaultBufferSize) {}
- explicit Parcel(std::vector<u8> data) : buffer(std::move(data)) {}
- virtual ~Parcel() = default;
-
- template <typename T>
- T Read() {
- static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable.");
- ASSERT(read_index + sizeof(T) <= buffer.size());
-
- T val;
- std::memcpy(&val, buffer.data() + read_index, sizeof(T));
- read_index += sizeof(T);
- read_index = Common::AlignUp(read_index, 4);
- return val;
- }
-
- template <typename T>
- T ReadUnaligned() {
- static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable.");
- ASSERT(read_index + sizeof(T) <= buffer.size());
-
- T val;
- std::memcpy(&val, buffer.data() + read_index, sizeof(T));
- read_index += sizeof(T);
- return val;
- }
-
- std::vector<u8> ReadBlock(std::size_t length) {
- ASSERT(read_index + length <= buffer.size());
- const u8* const begin = buffer.data() + read_index;
- const u8* const end = begin + length;
- std::vector<u8> data(begin, end);
- read_index += length;
- read_index = Common::AlignUp(read_index, 4);
- return data;
- }
-
- std::u16string ReadInterfaceToken() {
- [[maybe_unused]] const u32 unknown = Read<u32_le>();
- const u32 length = Read<u32_le>();
-
- std::u16string token{};
-
- for (u32 ch = 0; ch < length + 1; ++ch) {
- token.push_back(ReadUnaligned<u16_le>());
- }
-
- read_index = Common::AlignUp(read_index, 4);
-
- return token;
- }
-
- template <typename T>
- void Write(const T& val) {
- static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable.");
-
- if (buffer.size() < write_index + sizeof(T)) {
- buffer.resize(buffer.size() + sizeof(T) + DefaultBufferSize);
- }
-
- std::memcpy(buffer.data() + write_index, &val, sizeof(T));
- write_index += sizeof(T);
- write_index = Common::AlignUp(write_index, 4);
- }
-
- template <typename T>
- void WriteObject(const T& val) {
- static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable.");
-
- const u32_le size = static_cast<u32>(sizeof(val));
- Write(size);
- // TODO(Subv): Support file descriptors.
- Write<u32_le>(0); // Fd count.
- Write(val);
- }
-
- void Deserialize() {
- ASSERT(buffer.size() > sizeof(Header));
-
- Header header{};
- std::memcpy(&header, buffer.data(), sizeof(Header));
-
- read_index = header.data_offset;
- DeserializeData();
- }
-
- std::vector<u8> Serialize() {
- ASSERT(read_index == 0);
- write_index = sizeof(Header);
-
- SerializeData();
-
- Header header{};
- header.data_size = static_cast<u32_le>(write_index - sizeof(Header));
- header.data_offset = sizeof(Header);
- header.objects_size = 4;
- header.objects_offset = static_cast<u32>(sizeof(Header) + header.data_size);
- std::memcpy(buffer.data(), &header, sizeof(Header));
-
- return buffer;
- }
-
-protected:
- virtual void SerializeData() {}
-
- virtual void DeserializeData() {}
-
-private:
- struct Header {
- u32_le data_size;
- u32_le data_offset;
- u32_le objects_size;
- u32_le objects_offset;
- };
- static_assert(sizeof(Header) == 16, "ParcelHeader has wrong size");
-
- std::vector<u8> buffer;
- std::size_t read_index = 0;
- std::size_t write_index = 0;
-};
-
-class NativeWindow : public Parcel {
-public:
- explicit NativeWindow(u32 id) {
- data.id = id;
- }
- ~NativeWindow() override = default;
-
-protected:
- void SerializeData() override {
- Write(data);
- }
-
-private:
- struct Data {
- u32_le magic = 2;
- u32_le process_id = 1;
- u32_le id;
- INSERT_PADDING_WORDS(3);
- std::array<u8, 8> dispdrv = {'d', 'i', 's', 'p', 'd', 'r', 'v', '\0'};
- INSERT_PADDING_WORDS(2);
- };
- static_assert(sizeof(Data) == 0x28, "ParcelData has wrong size");
-
- Data data{};
-};
-
-class IGBPConnectRequestParcel : public Parcel {
-public:
- explicit IGBPConnectRequestParcel(std::vector<u8> buffer_) : Parcel(std::move(buffer_)) {
- Deserialize();
- }
-
- void DeserializeData() override {
- [[maybe_unused]] const std::u16string token = ReadInterfaceToken();
- data = Read<Data>();
- }
-
- struct Data {
- u32_le unk;
- u32_le api;
- u32_le producer_controlled_by_app;
- };
-
- Data data;
-};
-
-class IGBPConnectResponseParcel : public Parcel {
-public:
- explicit IGBPConnectResponseParcel(u32 width, u32 height) {
- data.width = width;
- data.height = height;
- }
- ~IGBPConnectResponseParcel() override = default;
-
-protected:
- void SerializeData() override {
- Write(data);
- }
-
-private:
- struct Data {
- u32_le width;
- u32_le height;
- u32_le transform_hint;
- u32_le num_pending_buffers;
- u32_le status;
- };
- static_assert(sizeof(Data) == 20, "ParcelData has wrong size");
-
- Data data{};
-};
-
-/// Represents a parcel containing one int '0' as its data
-/// Used by DetachBuffer and Disconnect
-class IGBPEmptyResponseParcel : public Parcel {
-protected:
- void SerializeData() override {
- Write(data);
- }
-
-private:
- struct Data {
- u32_le unk_0{};
- };
-
- Data data{};
-};
-
-class IGBPSetPreallocatedBufferRequestParcel : public Parcel {
-public:
- explicit IGBPSetPreallocatedBufferRequestParcel(std::vector<u8> buffer_)
- : Parcel(std::move(buffer_)) {
- Deserialize();
- }
-
- void DeserializeData() override {
- [[maybe_unused]] const std::u16string token = ReadInterfaceToken();
- data = Read<Data>();
- if (data.contains_object != 0) {
- buffer_container = Read<BufferContainer>();
- }
- }
-
- struct Data {
- u32_le slot;
- u32_le contains_object;
- };
-
- struct BufferContainer {
- u32_le graphic_buffer_length;
- INSERT_PADDING_WORDS(1);
- NVFlinger::IGBPBuffer buffer{};
- };
-
- Data data{};
- BufferContainer buffer_container{};
-};
-
-class IGBPSetPreallocatedBufferResponseParcel : public Parcel {
-protected:
- void SerializeData() override {
- // TODO(Subv): Find out what this means
- Write<u32>(0);
- }
-};
-
-class IGBPCancelBufferRequestParcel : public Parcel {
-public:
- explicit IGBPCancelBufferRequestParcel(std::vector<u8> buffer_) : Parcel(std::move(buffer_)) {
- Deserialize();
- }
-
- void DeserializeData() override {
- [[maybe_unused]] const std::u16string token = ReadInterfaceToken();
- data = Read<Data>();
- }
-
- struct Data {
- u32_le slot;
- Service::Nvidia::MultiFence multi_fence;
- };
-
- Data data;
-};
-
-class IGBPCancelBufferResponseParcel : public Parcel {
-protected:
- void SerializeData() override {
- Write<u32>(0); // Success
- }
-};
-
-class IGBPDequeueBufferRequestParcel : public Parcel {
-public:
- explicit IGBPDequeueBufferRequestParcel(std::vector<u8> buffer_) : Parcel(std::move(buffer_)) {
- Deserialize();
- }
-
- void DeserializeData() override {
- [[maybe_unused]] const std::u16string token = ReadInterfaceToken();
- data = Read<Data>();
- }
-
- struct Data {
- u32_le pixel_format;
- u32_le width;
- u32_le height;
- u32_le get_frame_timestamps;
- u32_le usage;
- };
-
- Data data;
-};
-
-class IGBPDequeueBufferResponseParcel : public Parcel {
-public:
- explicit IGBPDequeueBufferResponseParcel(u32 slot_, Nvidia::MultiFence& multi_fence_)
- : slot(slot_), multi_fence(multi_fence_) {}
-
-protected:
- void SerializeData() override {
- Write(slot);
- Write<u32_le>(1);
- WriteObject(multi_fence);
- Write<u32_le>(0);
- }
-
- u32_le slot;
- Service::Nvidia::MultiFence multi_fence;
-};
-
-class IGBPRequestBufferRequestParcel : public Parcel {
-public:
- explicit IGBPRequestBufferRequestParcel(std::vector<u8> buffer_) : Parcel(std::move(buffer_)) {
- Deserialize();
- }
-
- void DeserializeData() override {
- [[maybe_unused]] const std::u16string token = ReadInterfaceToken();
- slot = Read<u32_le>();
- }
-
- u32_le slot;
-};
-
-class IGBPRequestBufferResponseParcel : public Parcel {
-public:
- explicit IGBPRequestBufferResponseParcel(NVFlinger::IGBPBuffer buffer_) : buffer(buffer_) {}
- ~IGBPRequestBufferResponseParcel() override = default;
-
-protected:
- void SerializeData() override {
- // TODO(Subv): Figure out what this value means, writing non-zero here will make libnx
- // try to read an IGBPBuffer object from the parcel.
- Write<u32_le>(1);
- WriteObject(buffer);
- Write<u32_le>(0);
- }
-
- NVFlinger::IGBPBuffer buffer;
-};
-
-class IGBPQueueBufferRequestParcel : public Parcel {
-public:
- explicit IGBPQueueBufferRequestParcel(std::vector<u8> buffer_) : Parcel(std::move(buffer_)) {
- Deserialize();
- }
-
- void DeserializeData() override {
- [[maybe_unused]] const std::u16string token = ReadInterfaceToken();
- data = Read<Data>();
- }
-
- struct Data {
- u32_le slot;
- INSERT_PADDING_WORDS(3);
- u32_le timestamp;
- s32_le is_auto_timestamp;
- s32_le crop_top;
- s32_le crop_left;
- s32_le crop_right;
- s32_le crop_bottom;
- s32_le scaling_mode;
- NVFlinger::BufferQueue::BufferTransformFlags transform;
- u32_le sticky_transform;
- INSERT_PADDING_WORDS(1);
- u32_le swap_interval;
- Service::Nvidia::MultiFence multi_fence;
-
- Common::Rectangle<int> GetCropRect() const {
- return {crop_left, crop_top, crop_right, crop_bottom};
- }
- };
- static_assert(sizeof(Data) == 96, "ParcelData has wrong size");
-
- Data data;
-};
-
-class IGBPQueueBufferResponseParcel : public Parcel {
-public:
- explicit IGBPQueueBufferResponseParcel(u32 width, u32 height) {
- data.width = width;
- data.height = height;
- }
- ~IGBPQueueBufferResponseParcel() override = default;
-
-protected:
- void SerializeData() override {
- Write(data);
- }
-
-private:
- struct Data {
- u32_le width;
- u32_le height;
- u32_le transform_hint;
- u32_le num_pending_buffers;
- u32_le status;
- };
- static_assert(sizeof(Data) == 20, "ParcelData has wrong size");
-
- Data data{};
-};
-
-class IGBPQueryRequestParcel : public Parcel {
-public:
- explicit IGBPQueryRequestParcel(std::vector<u8> buffer_) : Parcel(std::move(buffer_)) {
- Deserialize();
- }
-
- void DeserializeData() override {
- [[maybe_unused]] const std::u16string token = ReadInterfaceToken();
- type = Read<u32_le>();
- }
-
- u32 type;
-};
-
-class IGBPQueryResponseParcel : public Parcel {
-public:
- explicit IGBPQueryResponseParcel(u32 value_) : value{value_} {}
- ~IGBPQueryResponseParcel() override = default;
-
-protected:
- void SerializeData() override {
- Write(value);
- }
+ constexpr explicit NativeWindow(u32 id_) : id{id_} {}
private:
- u32_le value;
+ const u32 magic = 2;
+ const u32 process_id = 1;
+ const u32 id;
+ INSERT_PADDING_WORDS(3);
+ std::array<u8, 8> dispdrv = {'d', 'i', 's', 'p', 'd', 'r', 'v', '\0'};
+ INSERT_PADDING_WORDS(2);
};
+static_assert(sizeof(NativeWindow) == 0x28, "NativeWindow has wrong size");
class IHOSBinderDriver final : public ServiceFramework<IHOSBinderDriver> {
public:
- explicit IHOSBinderDriver(Core::System& system_, NVFlinger::NVFlinger& nv_flinger_)
- : ServiceFramework{system_, "IHOSBinderDriver"}, nv_flinger(nv_flinger_) {
+ explicit IHOSBinderDriver(Core::System& system_, NVFlinger::HosBinderDriverServer& server_)
+ : ServiceFramework{system_, "IHOSBinderDriver", ServiceThreadType::CreateNew},
+ server(server_) {
static const FunctionInfo functions[] = {
{0, &IHOSBinderDriver::TransactParcel, "TransactParcel"},
{1, &IHOSBinderDriver::AdjustRefcount, "AdjustRefcount"},
@@ -508,147 +89,16 @@ public:
}
private:
- enum class TransactionId {
- RequestBuffer = 1,
- SetBufferCount = 2,
- DequeueBuffer = 3,
- DetachBuffer = 4,
- DetachNextBuffer = 5,
- AttachBuffer = 6,
- QueueBuffer = 7,
- CancelBuffer = 8,
- Query = 9,
- Connect = 10,
- Disconnect = 11,
-
- AllocateBuffers = 13,
- SetPreallocatedBuffer = 14,
-
- GetBufferHistory = 17
- };
-
void TransactParcel(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const u32 id = rp.Pop<u32>();
- const auto transaction = static_cast<TransactionId>(rp.Pop<u32>());
+ const auto transaction = static_cast<android::TransactionId>(rp.Pop<u32>());
const u32 flags = rp.Pop<u32>();
LOG_DEBUG(Service_VI, "called. id=0x{:08X} transaction={:X}, flags=0x{:08X}", id,
transaction, flags);
- auto& buffer_queue = *nv_flinger.FindBufferQueue(id);
-
- switch (transaction) {
- case TransactionId::Connect: {
- IGBPConnectRequestParcel request{ctx.ReadBuffer()};
- IGBPConnectResponseParcel response{static_cast<u32>(DisplayResolution::UndockedWidth),
- static_cast<u32>(DisplayResolution::UndockedHeight)};
-
- buffer_queue.Connect();
-
- ctx.WriteBuffer(response.Serialize());
- break;
- }
- case TransactionId::SetPreallocatedBuffer: {
- IGBPSetPreallocatedBufferRequestParcel request{ctx.ReadBuffer()};
-
- buffer_queue.SetPreallocatedBuffer(request.data.slot, request.buffer_container.buffer);
-
- IGBPSetPreallocatedBufferResponseParcel response{};
- ctx.WriteBuffer(response.Serialize());
- break;
- }
- case TransactionId::DequeueBuffer: {
- IGBPDequeueBufferRequestParcel request{ctx.ReadBuffer()};
- const u32 width{request.data.width};
- const u32 height{request.data.height};
-
- do {
- if (auto result = buffer_queue.DequeueBuffer(width, height); result) {
- // Buffer is available
- IGBPDequeueBufferResponseParcel response{result->first, *result->second};
- ctx.WriteBuffer(response.Serialize());
- break;
- }
- } while (buffer_queue.IsConnected());
-
- break;
- }
- case TransactionId::RequestBuffer: {
- IGBPRequestBufferRequestParcel request{ctx.ReadBuffer()};
-
- auto& buffer = buffer_queue.RequestBuffer(request.slot);
- IGBPRequestBufferResponseParcel response{buffer};
- ctx.WriteBuffer(response.Serialize());
-
- break;
- }
- case TransactionId::QueueBuffer: {
- IGBPQueueBufferRequestParcel request{ctx.ReadBuffer()};
-
- buffer_queue.QueueBuffer(request.data.slot, request.data.transform,
- request.data.GetCropRect(), request.data.swap_interval,
- request.data.multi_fence);
-
- IGBPQueueBufferResponseParcel response{1280, 720};
- ctx.WriteBuffer(response.Serialize());
- break;
- }
- case TransactionId::Query: {
- IGBPQueryRequestParcel request{ctx.ReadBuffer()};
-
- const u32 value =
- buffer_queue.Query(static_cast<NVFlinger::BufferQueue::QueryType>(request.type));
-
- IGBPQueryResponseParcel response{value};
- ctx.WriteBuffer(response.Serialize());
- break;
- }
- case TransactionId::CancelBuffer: {
- IGBPCancelBufferRequestParcel request{ctx.ReadBuffer()};
-
- buffer_queue.CancelBuffer(request.data.slot, request.data.multi_fence);
-
- IGBPCancelBufferResponseParcel response{};
- ctx.WriteBuffer(response.Serialize());
- break;
- }
- case TransactionId::Disconnect: {
- LOG_WARNING(Service_VI, "(STUBBED) called, transaction=Disconnect");
- const auto buffer = ctx.ReadBuffer();
-
- buffer_queue.Disconnect();
-
- IGBPEmptyResponseParcel response{};
- ctx.WriteBuffer(response.Serialize());
- break;
- }
- case TransactionId::DetachBuffer: {
- const auto buffer = ctx.ReadBuffer();
-
- IGBPEmptyResponseParcel response{};
- ctx.WriteBuffer(response.Serialize());
- break;
- }
- case TransactionId::SetBufferCount: {
- LOG_WARNING(Service_VI, "(STUBBED) called, transaction=SetBufferCount");
- [[maybe_unused]] const auto buffer = ctx.ReadBuffer();
-
- IGBPEmptyResponseParcel response{};
- ctx.WriteBuffer(response.Serialize());
- break;
- }
- case TransactionId::GetBufferHistory: {
- LOG_WARNING(Service_VI, "(STUBBED) called, transaction=GetBufferHistory");
- [[maybe_unused]] const auto buffer = ctx.ReadBuffer();
-
- IGBPEmptyResponseParcel response{};
- ctx.WriteBuffer(response.Serialize());
- break;
- }
- default:
- ASSERT_MSG(false, "Unimplemented");
- }
+ server.TryGetProducer(id)->Transact(ctx, transaction, flags);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
@@ -674,13 +124,13 @@ private:
LOG_WARNING(Service_VI, "(STUBBED) called id={}, unknown={:08X}", id, unknown);
- // TODO(Subv): Find out what this actually is.
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(ResultSuccess);
- rb.PushCopyObjects(nv_flinger.FindBufferQueue(id)->GetBufferWaitEvent());
+ rb.PushCopyObjects(server.TryGetProducer(id)->GetNativeHandle());
}
- NVFlinger::NVFlinger& nv_flinger;
+private:
+ NVFlinger::HosBinderDriverServer& server;
};
class ISystemDisplayService final : public ServiceFramework<ISystemDisplayService> {
@@ -937,7 +387,40 @@ private:
class IApplicationDisplayService final : public ServiceFramework<IApplicationDisplayService> {
public:
- explicit IApplicationDisplayService(Core::System& system_, NVFlinger::NVFlinger& nv_flinger_);
+ IApplicationDisplayService(Core::System& system_, NVFlinger::NVFlinger& nv_flinger_,
+ NVFlinger::HosBinderDriverServer& hos_binder_driver_server_)
+ : ServiceFramework{system_, "IApplicationDisplayService"}, nv_flinger{nv_flinger_},
+ hos_binder_driver_server{hos_binder_driver_server_} {
+
+ static const FunctionInfo functions[] = {
+ {100, &IApplicationDisplayService::GetRelayService, "GetRelayService"},
+ {101, &IApplicationDisplayService::GetSystemDisplayService, "GetSystemDisplayService"},
+ {102, &IApplicationDisplayService::GetManagerDisplayService,
+ "GetManagerDisplayService"},
+ {103, &IApplicationDisplayService::GetIndirectDisplayTransactionService,
+ "GetIndirectDisplayTransactionService"},
+ {1000, &IApplicationDisplayService::ListDisplays, "ListDisplays"},
+ {1010, &IApplicationDisplayService::OpenDisplay, "OpenDisplay"},
+ {1011, &IApplicationDisplayService::OpenDefaultDisplay, "OpenDefaultDisplay"},
+ {1020, &IApplicationDisplayService::CloseDisplay, "CloseDisplay"},
+ {1101, &IApplicationDisplayService::SetDisplayEnabled, "SetDisplayEnabled"},
+ {1102, &IApplicationDisplayService::GetDisplayResolution, "GetDisplayResolution"},
+ {2020, &IApplicationDisplayService::OpenLayer, "OpenLayer"},
+ {2021, &IApplicationDisplayService::CloseLayer, "CloseLayer"},
+ {2030, &IApplicationDisplayService::CreateStrayLayer, "CreateStrayLayer"},
+ {2031, &IApplicationDisplayService::DestroyStrayLayer, "DestroyStrayLayer"},
+ {2101, &IApplicationDisplayService::SetLayerScalingMode, "SetLayerScalingMode"},
+ {2102, &IApplicationDisplayService::ConvertScalingMode, "ConvertScalingMode"},
+ {2450, &IApplicationDisplayService::GetIndirectLayerImageMap,
+ "GetIndirectLayerImageMap"},
+ {2451, nullptr, "GetIndirectLayerImageCropMap"},
+ {2460, &IApplicationDisplayService::GetIndirectLayerImageRequiredMemoryInfo,
+ "GetIndirectLayerImageRequiredMemoryInfo"},
+ {5202, &IApplicationDisplayService::GetDisplayVsyncEvent, "GetDisplayVsyncEvent"},
+ {5203, nullptr, "GetDisplayVsyncEventForDebug"},
+ };
+ RegisterHandlers(functions);
+ }
private:
enum class ConvertedScaleMode : u64 {
@@ -961,7 +444,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
- rb.PushIpcInterface<IHOSBinderDriver>(system, nv_flinger);
+ rb.PushIpcInterface<IHOSBinderDriver>(system, hos_binder_driver_server);
}
void GetSystemDisplayService(Kernel::HLERequestContext& ctx) {
@@ -985,7 +468,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
- rb.PushIpcInterface<IHOSBinderDriver>(system, nv_flinger);
+ rb.PushIpcInterface<IHOSBinderDriver>(system, hos_binder_driver_server);
}
void OpenDisplay(Kernel::HLERequestContext& ctx) {
@@ -1089,7 +572,7 @@ private:
void ListDisplays(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_VI, "(STUBBED) called");
- DisplayInfo display_info;
+ const DisplayInfo display_info;
ctx.WriteBuffer(&display_info, sizeof(DisplayInfo));
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(ResultSuccess);
@@ -1124,8 +607,8 @@ private:
return;
}
- NativeWindow native_window{*buffer_queue_id};
- const auto buffer_size = ctx.WriteBuffer(native_window.Serialize());
+ const auto parcel = android::Parcel{NativeWindow{*buffer_queue_id}};
+ const auto buffer_size = ctx.WriteBuffer(parcel.Serialize());
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(ResultSuccess);
@@ -1170,8 +653,8 @@ private:
return;
}
- NativeWindow native_window{*buffer_queue_id};
- const auto buffer_size = ctx.WriteBuffer(native_window.Serialize());
+ const auto parcel = android::Parcel{NativeWindow{*buffer_queue_id}};
+ const auto buffer_size = ctx.WriteBuffer(parcel.Serialize());
IPC::ResponseBuilder rb{ctx, 6};
rb.Push(ResultSuccess);
@@ -1287,39 +770,9 @@ private:
}
NVFlinger::NVFlinger& nv_flinger;
+ NVFlinger::HosBinderDriverServer& hos_binder_driver_server;
};
-IApplicationDisplayService::IApplicationDisplayService(Core::System& system_,
- NVFlinger::NVFlinger& nv_flinger_)
- : ServiceFramework{system_, "IApplicationDisplayService"}, nv_flinger{nv_flinger_} {
- static const FunctionInfo functions[] = {
- {100, &IApplicationDisplayService::GetRelayService, "GetRelayService"},
- {101, &IApplicationDisplayService::GetSystemDisplayService, "GetSystemDisplayService"},
- {102, &IApplicationDisplayService::GetManagerDisplayService, "GetManagerDisplayService"},
- {103, &IApplicationDisplayService::GetIndirectDisplayTransactionService,
- "GetIndirectDisplayTransactionService"},
- {1000, &IApplicationDisplayService::ListDisplays, "ListDisplays"},
- {1010, &IApplicationDisplayService::OpenDisplay, "OpenDisplay"},
- {1011, &IApplicationDisplayService::OpenDefaultDisplay, "OpenDefaultDisplay"},
- {1020, &IApplicationDisplayService::CloseDisplay, "CloseDisplay"},
- {1101, &IApplicationDisplayService::SetDisplayEnabled, "SetDisplayEnabled"},
- {1102, &IApplicationDisplayService::GetDisplayResolution, "GetDisplayResolution"},
- {2020, &IApplicationDisplayService::OpenLayer, "OpenLayer"},
- {2021, &IApplicationDisplayService::CloseLayer, "CloseLayer"},
- {2030, &IApplicationDisplayService::CreateStrayLayer, "CreateStrayLayer"},
- {2031, &IApplicationDisplayService::DestroyStrayLayer, "DestroyStrayLayer"},
- {2101, &IApplicationDisplayService::SetLayerScalingMode, "SetLayerScalingMode"},
- {2102, &IApplicationDisplayService::ConvertScalingMode, "ConvertScalingMode"},
- {2450, &IApplicationDisplayService::GetIndirectLayerImageMap, "GetIndirectLayerImageMap"},
- {2451, nullptr, "GetIndirectLayerImageCropMap"},
- {2460, &IApplicationDisplayService::GetIndirectLayerImageRequiredMemoryInfo,
- "GetIndirectLayerImageRequiredMemoryInfo"},
- {5202, &IApplicationDisplayService::GetDisplayVsyncEvent, "GetDisplayVsyncEvent"},
- {5203, nullptr, "GetDisplayVsyncEventForDebug"},
- };
- RegisterHandlers(functions);
-}
-
static bool IsValidServiceAccess(Permission permission, Policy policy) {
if (permission == Permission::User) {
return policy == Policy::User;
@@ -1333,7 +786,9 @@ static bool IsValidServiceAccess(Permission permission, Policy policy) {
}
void detail::GetDisplayServiceImpl(Kernel::HLERequestContext& ctx, Core::System& system,
- NVFlinger::NVFlinger& nv_flinger, Permission permission) {
+ NVFlinger::NVFlinger& nv_flinger,
+ NVFlinger::HosBinderDriverServer& hos_binder_driver_server,
+ Permission permission) {
IPC::RequestParser rp{ctx};
const auto policy = rp.PopEnum<Policy>();
@@ -1346,14 +801,18 @@ void detail::GetDisplayServiceImpl(Kernel::HLERequestContext& ctx, Core::System&
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
- rb.PushIpcInterface<IApplicationDisplayService>(system, nv_flinger);
+ rb.PushIpcInterface<IApplicationDisplayService>(system, nv_flinger, hos_binder_driver_server);
}
void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system,
- NVFlinger::NVFlinger& nv_flinger) {
- std::make_shared<VI_M>(system, nv_flinger)->InstallAsService(service_manager);
- std::make_shared<VI_S>(system, nv_flinger)->InstallAsService(service_manager);
- std::make_shared<VI_U>(system, nv_flinger)->InstallAsService(service_manager);
+ NVFlinger::NVFlinger& nv_flinger,
+ NVFlinger::HosBinderDriverServer& hos_binder_driver_server) {
+ std::make_shared<VI_M>(system, nv_flinger, hos_binder_driver_server)
+ ->InstallAsService(service_manager);
+ std::make_shared<VI_S>(system, nv_flinger, hos_binder_driver_server)
+ ->InstallAsService(service_manager);
+ std::make_shared<VI_U>(system, nv_flinger, hos_binder_driver_server)
+ ->InstallAsService(service_manager);
}
} // namespace Service::VI
diff --git a/src/core/hle/service/vi/vi.h b/src/core/hle/service/vi/vi.h
index 2fd7f8e61..d68f2646b 100644
--- a/src/core/hle/service/vi/vi.h
+++ b/src/core/hle/service/vi/vi.h
@@ -15,8 +15,9 @@ class HLERequestContext;
}
namespace Service::NVFlinger {
+class HosBinderDriverServer;
class NVFlinger;
-}
+} // namespace Service::NVFlinger
namespace Service::SM {
class ServiceManager;
@@ -47,11 +48,14 @@ enum class Policy {
namespace detail {
void GetDisplayServiceImpl(Kernel::HLERequestContext& ctx, Core::System& system,
- NVFlinger::NVFlinger& nv_flinger, Permission permission);
+ NVFlinger::NVFlinger& nv_flinger,
+ NVFlinger::HosBinderDriverServer& hos_binder_driver_server,
+ Permission permission);
} // namespace detail
/// Registers all VI services with the specified service manager.
void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system,
- NVFlinger::NVFlinger& nv_flinger);
+ NVFlinger::NVFlinger& nv_flinger,
+ NVFlinger::HosBinderDriverServer& hos_binder_driver_server);
} // namespace Service::VI
diff --git a/src/core/hle/service/vi/vi_m.cpp b/src/core/hle/service/vi/vi_m.cpp
index 87db1c416..be0255f3d 100644
--- a/src/core/hle/service/vi/vi_m.cpp
+++ b/src/core/hle/service/vi/vi_m.cpp
@@ -8,8 +8,10 @@
namespace Service::VI {
-VI_M::VI_M(Core::System& system_, NVFlinger::NVFlinger& nv_flinger_)
- : ServiceFramework{system_, "vi:m"}, nv_flinger{nv_flinger_} {
+VI_M::VI_M(Core::System& system_, NVFlinger::NVFlinger& nv_flinger_,
+ NVFlinger::HosBinderDriverServer& hos_binder_driver_server_)
+ : ServiceFramework{system_, "vi:m"}, nv_flinger{nv_flinger_}, hos_binder_driver_server{
+ hos_binder_driver_server_} {
static const FunctionInfo functions[] = {
{2, &VI_M::GetDisplayService, "GetDisplayService"},
{3, nullptr, "GetDisplayServiceWithProxyNameExchange"},
@@ -22,7 +24,8 @@ VI_M::~VI_M() = default;
void VI_M::GetDisplayService(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_VI, "called");
- detail::GetDisplayServiceImpl(ctx, system, nv_flinger, Permission::Manager);
+ detail::GetDisplayServiceImpl(ctx, system, nv_flinger, hos_binder_driver_server,
+ Permission::Manager);
}
} // namespace Service::VI
diff --git a/src/core/hle/service/vi/vi_m.h b/src/core/hle/service/vi/vi_m.h
index d79c41beb..efbd34e09 100644
--- a/src/core/hle/service/vi/vi_m.h
+++ b/src/core/hle/service/vi/vi_m.h
@@ -15,20 +15,23 @@ class HLERequestContext;
}
namespace Service::NVFlinger {
+class HosBinderDriverServer;
class NVFlinger;
-}
+} // namespace Service::NVFlinger
namespace Service::VI {
class VI_M final : public ServiceFramework<VI_M> {
public:
- explicit VI_M(Core::System& system_, NVFlinger::NVFlinger& nv_flinger_);
+ explicit VI_M(Core::System& system_, NVFlinger::NVFlinger& nv_flinger_,
+ NVFlinger::HosBinderDriverServer& hos_binder_driver_server_);
~VI_M() override;
private:
void GetDisplayService(Kernel::HLERequestContext& ctx);
NVFlinger::NVFlinger& nv_flinger;
+ NVFlinger::HosBinderDriverServer& hos_binder_driver_server;
};
} // namespace Service::VI
diff --git a/src/core/hle/service/vi/vi_s.cpp b/src/core/hle/service/vi/vi_s.cpp
index 5cd22f7df..7996a6811 100644
--- a/src/core/hle/service/vi/vi_s.cpp
+++ b/src/core/hle/service/vi/vi_s.cpp
@@ -8,8 +8,10 @@
namespace Service::VI {
-VI_S::VI_S(Core::System& system_, NVFlinger::NVFlinger& nv_flinger_)
- : ServiceFramework{system_, "vi:s"}, nv_flinger{nv_flinger_} {
+VI_S::VI_S(Core::System& system_, NVFlinger::NVFlinger& nv_flinger_,
+ NVFlinger::HosBinderDriverServer& hos_binder_driver_server_)
+ : ServiceFramework{system_, "vi:s"}, nv_flinger{nv_flinger_}, hos_binder_driver_server{
+ hos_binder_driver_server_} {
static const FunctionInfo functions[] = {
{1, &VI_S::GetDisplayService, "GetDisplayService"},
{3, nullptr, "GetDisplayServiceWithProxyNameExchange"},
@@ -22,7 +24,8 @@ VI_S::~VI_S() = default;
void VI_S::GetDisplayService(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_VI, "called");
- detail::GetDisplayServiceImpl(ctx, system, nv_flinger, Permission::System);
+ detail::GetDisplayServiceImpl(ctx, system, nv_flinger, hos_binder_driver_server,
+ Permission::System);
}
} // namespace Service::VI
diff --git a/src/core/hle/service/vi/vi_s.h b/src/core/hle/service/vi/vi_s.h
index 5f1f8f290..3812c5061 100644
--- a/src/core/hle/service/vi/vi_s.h
+++ b/src/core/hle/service/vi/vi_s.h
@@ -15,20 +15,23 @@ class HLERequestContext;
}
namespace Service::NVFlinger {
+class HosBinderDriverServer;
class NVFlinger;
-}
+} // namespace Service::NVFlinger
namespace Service::VI {
class VI_S final : public ServiceFramework<VI_S> {
public:
- explicit VI_S(Core::System& system_, NVFlinger::NVFlinger& nv_flinger_);
+ explicit VI_S(Core::System& system_, NVFlinger::NVFlinger& nv_flinger_,
+ NVFlinger::HosBinderDriverServer& hos_binder_driver_server_);
~VI_S() override;
private:
void GetDisplayService(Kernel::HLERequestContext& ctx);
NVFlinger::NVFlinger& nv_flinger;
+ NVFlinger::HosBinderDriverServer& hos_binder_driver_server;
};
} // namespace Service::VI
diff --git a/src/core/hle/service/vi/vi_u.cpp b/src/core/hle/service/vi/vi_u.cpp
index 0079d51f0..57c888313 100644
--- a/src/core/hle/service/vi/vi_u.cpp
+++ b/src/core/hle/service/vi/vi_u.cpp
@@ -8,8 +8,10 @@
namespace Service::VI {
-VI_U::VI_U(Core::System& system_, NVFlinger::NVFlinger& nv_flinger_)
- : ServiceFramework{system_, "vi:u"}, nv_flinger{nv_flinger_} {
+VI_U::VI_U(Core::System& system_, NVFlinger::NVFlinger& nv_flinger_,
+ NVFlinger::HosBinderDriverServer& hos_binder_driver_server_)
+ : ServiceFramework{system_, "vi:u"}, nv_flinger{nv_flinger_}, hos_binder_driver_server{
+ hos_binder_driver_server_} {
static const FunctionInfo functions[] = {
{0, &VI_U::GetDisplayService, "GetDisplayService"},
{1, nullptr, "GetDisplayServiceWithProxyNameExchange"},
@@ -22,7 +24,8 @@ VI_U::~VI_U() = default;
void VI_U::GetDisplayService(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_VI, "called");
- detail::GetDisplayServiceImpl(ctx, system, nv_flinger, Permission::User);
+ detail::GetDisplayServiceImpl(ctx, system, nv_flinger, hos_binder_driver_server,
+ Permission::User);
}
} // namespace Service::VI
diff --git a/src/core/hle/service/vi/vi_u.h b/src/core/hle/service/vi/vi_u.h
index 8e3885c73..b08e56576 100644
--- a/src/core/hle/service/vi/vi_u.h
+++ b/src/core/hle/service/vi/vi_u.h
@@ -15,20 +15,23 @@ class HLERequestContext;
}
namespace Service::NVFlinger {
+class HosBinderDriverServer;
class NVFlinger;
-}
+} // namespace Service::NVFlinger
namespace Service::VI {
class VI_U final : public ServiceFramework<VI_U> {
public:
- explicit VI_U(Core::System& system_, NVFlinger::NVFlinger& nv_flinger_);
+ explicit VI_U(Core::System& system_, NVFlinger::NVFlinger& nv_flinger_,
+ NVFlinger::HosBinderDriverServer& hos_binder_driver_server_);
~VI_U() override;
private:
void GetDisplayService(Kernel::HLERequestContext& ctx);
NVFlinger::NVFlinger& nv_flinger;
+ NVFlinger::HosBinderDriverServer& hos_binder_driver_server;
};
} // namespace Service::VI
diff --git a/src/core/network/network.cpp b/src/core/network/network.cpp
index a3e0664b9..0784a165d 100644
--- a/src/core/network/network.cpp
+++ b/src/core/network/network.cpp
@@ -600,6 +600,10 @@ Errno Socket::SetReuseAddr(bool enable) {
return SetSockOpt<u32>(fd, SO_REUSEADDR, enable ? 1 : 0);
}
+Errno Socket::SetKeepAlive(bool enable) {
+ return SetSockOpt<u32>(fd, SO_KEEPALIVE, enable ? 1 : 0);
+}
+
Errno Socket::SetBroadcast(bool enable) {
return SetSockOpt<u32>(fd, SO_BROADCAST, enable ? 1 : 0);
}
diff --git a/src/core/network/sockets.h b/src/core/network/sockets.h
index 5e39e7c54..caaefce7c 100644
--- a/src/core/network/sockets.h
+++ b/src/core/network/sockets.h
@@ -67,6 +67,8 @@ public:
Errno SetReuseAddr(bool enable);
+ Errno SetKeepAlive(bool enable);
+
Errno SetBroadcast(bool enable);
Errno SetSndBuf(u32 value);
diff --git a/src/core/perf_stats.cpp b/src/core/perf_stats.cpp
index 52c43c857..6ef459b7a 100644
--- a/src/core/perf_stats.cpp
+++ b/src/core/perf_stats.cpp
@@ -53,13 +53,13 @@ PerfStats::~PerfStats() {
}
void PerfStats::BeginSystemFrame() {
- std::lock_guard lock{object_mutex};
+ std::scoped_lock lock{object_mutex};
frame_begin = Clock::now();
}
void PerfStats::EndSystemFrame() {
- std::lock_guard lock{object_mutex};
+ std::scoped_lock lock{object_mutex};
auto frame_end = Clock::now();
const auto frame_time = frame_end - frame_begin;
@@ -79,7 +79,7 @@ void PerfStats::EndGameFrame() {
}
double PerfStats::GetMeanFrametime() const {
- std::lock_guard lock{object_mutex};
+ std::scoped_lock lock{object_mutex};
if (current_index <= IgnoreFrames) {
return 0;
@@ -91,7 +91,7 @@ double PerfStats::GetMeanFrametime() const {
}
PerfStatsResults PerfStats::GetAndResetStats(microseconds current_system_time_us) {
- std::lock_guard lock{object_mutex};
+ std::scoped_lock lock{object_mutex};
const auto now = Clock::now();
// Walltime elapsed since stats were reset
@@ -120,7 +120,7 @@ PerfStatsResults PerfStats::GetAndResetStats(microseconds current_system_time_us
}
double PerfStats::GetLastFrameTimeScale() const {
- std::lock_guard lock{object_mutex};
+ std::scoped_lock lock{object_mutex};
constexpr double FRAME_LENGTH = 1.0 / 60;
return duration_cast<DoubleSecs>(previous_frame_length).count() / FRAME_LENGTH;
diff --git a/src/core/tools/freezer.cpp b/src/core/tools/freezer.cpp
index 032c71aff..c81dc0e52 100644
--- a/src/core/tools/freezer.cpp
+++ b/src/core/tools/freezer.cpp
@@ -80,7 +80,7 @@ bool Freezer::IsActive() const {
}
void Freezer::Clear() {
- std::lock_guard lock{entries_mutex};
+ std::scoped_lock lock{entries_mutex};
LOG_DEBUG(Common_Memory, "Clearing all frozen memory values.");
@@ -88,7 +88,7 @@ void Freezer::Clear() {
}
u64 Freezer::Freeze(VAddr address, u32 width) {
- std::lock_guard lock{entries_mutex};
+ std::scoped_lock lock{entries_mutex};
const auto current_value = MemoryReadWidth(memory, width, address);
entries.push_back({address, width, current_value});
@@ -101,7 +101,7 @@ u64 Freezer::Freeze(VAddr address, u32 width) {
}
void Freezer::Unfreeze(VAddr address) {
- std::lock_guard lock{entries_mutex};
+ std::scoped_lock lock{entries_mutex};
LOG_DEBUG(Common_Memory, "Unfreezing memory for address={:016X}", address);
@@ -109,13 +109,13 @@ void Freezer::Unfreeze(VAddr address) {
}
bool Freezer::IsFrozen(VAddr address) const {
- std::lock_guard lock{entries_mutex};
+ std::scoped_lock lock{entries_mutex};
return FindEntry(address) != entries.cend();
}
void Freezer::SetFrozenValue(VAddr address, u64 value) {
- std::lock_guard lock{entries_mutex};
+ std::scoped_lock lock{entries_mutex};
const auto iter = FindEntry(address);
@@ -132,7 +132,7 @@ void Freezer::SetFrozenValue(VAddr address, u64 value) {
}
std::optional<Freezer::Entry> Freezer::GetEntry(VAddr address) const {
- std::lock_guard lock{entries_mutex};
+ std::scoped_lock lock{entries_mutex};
const auto iter = FindEntry(address);
@@ -144,7 +144,7 @@ std::optional<Freezer::Entry> Freezer::GetEntry(VAddr address) const {
}
std::vector<Freezer::Entry> Freezer::GetEntries() const {
- std::lock_guard lock{entries_mutex};
+ std::scoped_lock lock{entries_mutex};
return entries;
}
@@ -165,7 +165,7 @@ void Freezer::FrameCallback(std::uintptr_t, std::chrono::nanoseconds ns_late) {
return;
}
- std::lock_guard lock{entries_mutex};
+ std::scoped_lock lock{entries_mutex};
for (const auto& entry : entries) {
LOG_DEBUG(Common_Memory,
@@ -178,7 +178,7 @@ void Freezer::FrameCallback(std::uintptr_t, std::chrono::nanoseconds ns_late) {
}
void Freezer::FillEntryReads() {
- std::lock_guard lock{entries_mutex};
+ std::scoped_lock lock{entries_mutex};
LOG_DEBUG(Common_Memory, "Updating memory freeze entries to current values.");
diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp
index 64edf53e4..a5c63e74a 100644
--- a/src/input_common/drivers/sdl_driver.cpp
+++ b/src/input_common/drivers/sdl_driver.cpp
@@ -62,7 +62,7 @@ public:
bool UpdateMotion(SDL_ControllerSensorEvent event) {
constexpr float gravity_constant = 9.80665f;
- std::lock_guard lock{mutex};
+ std::scoped_lock lock{mutex};
const u64 time_difference = event.timestamp - last_motion_update;
last_motion_update = event.timestamp;
switch (event.sensor) {
@@ -241,7 +241,7 @@ private:
};
std::shared_ptr<SDLJoystick> SDLDriver::GetSDLJoystickByGUID(const std::string& guid, int port) {
- std::lock_guard lock{joystick_map_mutex};
+ std::scoped_lock lock{joystick_map_mutex};
const auto it = joystick_map.find(guid);
if (it != joystick_map.end()) {
@@ -263,7 +263,7 @@ std::shared_ptr<SDLJoystick> SDLDriver::GetSDLJoystickBySDLID(SDL_JoystickID sdl
auto sdl_joystick = SDL_JoystickFromInstanceID(sdl_id);
const std::string guid = GetGUID(sdl_joystick);
- std::lock_guard lock{joystick_map_mutex};
+ std::scoped_lock lock{joystick_map_mutex};
const auto map_it = joystick_map.find(guid);
if (map_it == joystick_map.end()) {
@@ -297,7 +297,7 @@ void SDLDriver::InitJoystick(int joystick_index) {
const std::string guid = GetGUID(sdl_joystick);
- std::lock_guard lock{joystick_map_mutex};
+ std::scoped_lock lock{joystick_map_mutex};
if (joystick_map.find(guid) == joystick_map.end()) {
auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick, sdl_gamecontroller);
PreSetController(joystick->GetPadIdentifier());
@@ -326,7 +326,7 @@ void SDLDriver::InitJoystick(int joystick_index) {
void SDLDriver::CloseJoystick(SDL_Joystick* sdl_joystick) {
const std::string guid = GetGUID(sdl_joystick);
- std::lock_guard lock{joystick_map_mutex};
+ std::scoped_lock lock{joystick_map_mutex};
// This call to guid is safe since the joystick is guaranteed to be in the map
const auto& joystick_guid_list = joystick_map[guid];
const auto joystick_it = std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(),
@@ -392,7 +392,7 @@ void SDLDriver::HandleGameControllerEvent(const SDL_Event& event) {
}
void SDLDriver::CloseJoysticks() {
- std::lock_guard lock{joystick_map_mutex};
+ std::scoped_lock lock{joystick_map_mutex};
joystick_map.clear();
}
diff --git a/src/input_common/helpers/udp_protocol.h b/src/input_common/helpers/udp_protocol.h
index 9c205e944..597f51cd3 100644
--- a/src/input_common/helpers/udp_protocol.h
+++ b/src/input_common/helpers/udp_protocol.h
@@ -8,8 +8,17 @@
#include <optional>
#include <type_traits>
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 4701) // Potentially uninitialized local variable 'result' used
+#endif
+
#include <boost/crc.hpp>
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
#include "common/swap.h"
namespace InputCommon::CemuhookUDP {
diff --git a/src/input_common/input_engine.cpp b/src/input_common/input_engine.cpp
index 738022ece..a16cca33d 100644
--- a/src/input_common/input_engine.cpp
+++ b/src/input_common/input_engine.cpp
@@ -8,37 +8,37 @@
namespace InputCommon {
void InputEngine::PreSetController(const PadIdentifier& identifier) {
- std::lock_guard lock{mutex};
+ std::scoped_lock lock{mutex};
controller_list.try_emplace(identifier);
}
void InputEngine::PreSetButton(const PadIdentifier& identifier, int button) {
- std::lock_guard lock{mutex};
+ std::scoped_lock lock{mutex};
ControllerData& controller = controller_list.at(identifier);
controller.buttons.try_emplace(button, false);
}
void InputEngine::PreSetHatButton(const PadIdentifier& identifier, int button) {
- std::lock_guard lock{mutex};
+ std::scoped_lock lock{mutex};
ControllerData& controller = controller_list.at(identifier);
controller.hat_buttons.try_emplace(button, u8{0});
}
void InputEngine::PreSetAxis(const PadIdentifier& identifier, int axis) {
- std::lock_guard lock{mutex};
+ std::scoped_lock lock{mutex};
ControllerData& controller = controller_list.at(identifier);
controller.axes.try_emplace(axis, 0.0f);
}
void InputEngine::PreSetMotion(const PadIdentifier& identifier, int motion) {
- std::lock_guard lock{mutex};
+ std::scoped_lock lock{mutex};
ControllerData& controller = controller_list.at(identifier);
controller.motions.try_emplace(motion);
}
void InputEngine::SetButton(const PadIdentifier& identifier, int button, bool value) {
{
- std::lock_guard lock{mutex};
+ std::scoped_lock lock{mutex};
ControllerData& controller = controller_list.at(identifier);
if (!configuring) {
controller.buttons.insert_or_assign(button, value);
@@ -49,7 +49,7 @@ void InputEngine::SetButton(const PadIdentifier& identifier, int button, bool va
void InputEngine::SetHatButton(const PadIdentifier& identifier, int button, u8 value) {
{
- std::lock_guard lock{mutex};
+ std::scoped_lock lock{mutex};
ControllerData& controller = controller_list.at(identifier);
if (!configuring) {
controller.hat_buttons.insert_or_assign(button, value);
@@ -60,7 +60,7 @@ void InputEngine::SetHatButton(const PadIdentifier& identifier, int button, u8 v
void InputEngine::SetAxis(const PadIdentifier& identifier, int axis, f32 value) {
{
- std::lock_guard lock{mutex};
+ std::scoped_lock lock{mutex};
ControllerData& controller = controller_list.at(identifier);
if (!configuring) {
controller.axes.insert_or_assign(axis, value);
@@ -71,7 +71,7 @@ void InputEngine::SetAxis(const PadIdentifier& identifier, int axis, f32 value)
void InputEngine::SetBattery(const PadIdentifier& identifier, Common::Input::BatteryLevel value) {
{
- std::lock_guard lock{mutex};
+ std::scoped_lock lock{mutex};
ControllerData& controller = controller_list.at(identifier);
if (!configuring) {
controller.battery = value;
@@ -82,7 +82,7 @@ void InputEngine::SetBattery(const PadIdentifier& identifier, Common::Input::Bat
void InputEngine::SetMotion(const PadIdentifier& identifier, int motion, const BasicMotion& value) {
{
- std::lock_guard lock{mutex};
+ std::scoped_lock lock{mutex};
ControllerData& controller = controller_list.at(identifier);
if (!configuring) {
controller.motions.insert_or_assign(motion, value);
@@ -92,7 +92,7 @@ void InputEngine::SetMotion(const PadIdentifier& identifier, int motion, const B
}
bool InputEngine::GetButton(const PadIdentifier& identifier, int button) const {
- std::lock_guard lock{mutex};
+ std::scoped_lock lock{mutex};
const auto controller_iter = controller_list.find(identifier);
if (controller_iter == controller_list.cend()) {
LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.RawString(),
@@ -109,7 +109,7 @@ bool InputEngine::GetButton(const PadIdentifier& identifier, int button) const {
}
bool InputEngine::GetHatButton(const PadIdentifier& identifier, int button, u8 direction) const {
- std::lock_guard lock{mutex};
+ std::scoped_lock lock{mutex};
const auto controller_iter = controller_list.find(identifier);
if (controller_iter == controller_list.cend()) {
LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.RawString(),
@@ -126,7 +126,7 @@ bool InputEngine::GetHatButton(const PadIdentifier& identifier, int button, u8 d
}
f32 InputEngine::GetAxis(const PadIdentifier& identifier, int axis) const {
- std::lock_guard lock{mutex};
+ std::scoped_lock lock{mutex};
const auto controller_iter = controller_list.find(identifier);
if (controller_iter == controller_list.cend()) {
LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.RawString(),
@@ -143,7 +143,7 @@ f32 InputEngine::GetAxis(const PadIdentifier& identifier, int axis) const {
}
Common::Input::BatteryLevel InputEngine::GetBattery(const PadIdentifier& identifier) const {
- std::lock_guard lock{mutex};
+ std::scoped_lock lock{mutex};
const auto controller_iter = controller_list.find(identifier);
if (controller_iter == controller_list.cend()) {
LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.RawString(),
@@ -155,7 +155,7 @@ Common::Input::BatteryLevel InputEngine::GetBattery(const PadIdentifier& identif
}
BasicMotion InputEngine::GetMotion(const PadIdentifier& identifier, int motion) const {
- std::lock_guard lock{mutex};
+ std::scoped_lock lock{mutex};
const auto controller_iter = controller_list.find(identifier);
if (controller_iter == controller_list.cend()) {
LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.RawString(),
@@ -186,7 +186,7 @@ void InputEngine::ResetAnalogState() {
}
void InputEngine::TriggerOnButtonChange(const PadIdentifier& identifier, int button, bool value) {
- std::lock_guard lock{mutex_callback};
+ std::scoped_lock lock{mutex_callback};
for (const auto& poller_pair : callback_list) {
const InputIdentifier& poller = poller_pair.second;
if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Button, button)) {
@@ -214,7 +214,7 @@ void InputEngine::TriggerOnButtonChange(const PadIdentifier& identifier, int but
}
void InputEngine::TriggerOnHatButtonChange(const PadIdentifier& identifier, int button, u8 value) {
- std::lock_guard lock{mutex_callback};
+ std::scoped_lock lock{mutex_callback};
for (const auto& poller_pair : callback_list) {
const InputIdentifier& poller = poller_pair.second;
if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::HatButton, button)) {
@@ -243,7 +243,7 @@ void InputEngine::TriggerOnHatButtonChange(const PadIdentifier& identifier, int
}
void InputEngine::TriggerOnAxisChange(const PadIdentifier& identifier, int axis, f32 value) {
- std::lock_guard lock{mutex_callback};
+ std::scoped_lock lock{mutex_callback};
for (const auto& poller_pair : callback_list) {
const InputIdentifier& poller = poller_pair.second;
if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Analog, axis)) {
@@ -270,7 +270,7 @@ void InputEngine::TriggerOnAxisChange(const PadIdentifier& identifier, int axis,
void InputEngine::TriggerOnBatteryChange(const PadIdentifier& identifier,
[[maybe_unused]] Common::Input::BatteryLevel value) {
- std::lock_guard lock{mutex_callback};
+ std::scoped_lock lock{mutex_callback};
for (const auto& poller_pair : callback_list) {
const InputIdentifier& poller = poller_pair.second;
if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Battery, 0)) {
@@ -284,7 +284,7 @@ void InputEngine::TriggerOnBatteryChange(const PadIdentifier& identifier,
void InputEngine::TriggerOnMotionChange(const PadIdentifier& identifier, int motion,
const BasicMotion& value) {
- std::lock_guard lock{mutex_callback};
+ std::scoped_lock lock{mutex_callback};
for (const auto& poller_pair : callback_list) {
const InputIdentifier& poller = poller_pair.second;
if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Motion, motion)) {
@@ -346,18 +346,18 @@ const std::string& InputEngine::GetEngineName() const {
}
int InputEngine::SetCallback(InputIdentifier input_identifier) {
- std::lock_guard lock{mutex_callback};
+ std::scoped_lock lock{mutex_callback};
callback_list.insert_or_assign(last_callback_key, std::move(input_identifier));
return last_callback_key++;
}
void InputEngine::SetMappingCallback(MappingCallback callback) {
- std::lock_guard lock{mutex_callback};
+ std::scoped_lock lock{mutex_callback};
mapping_callback = std::move(callback);
}
void InputEngine::DeleteCallback(int key) {
- std::lock_guard lock{mutex_callback};
+ std::scoped_lock lock{mutex_callback};
const auto& iterator = callback_list.find(key);
if (iterator == callback_list.end()) {
LOG_ERROR(Input, "Tried to delete non-existent callback {}", key);
diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_context_get_set.cpp b/src/shader_recompiler/backend/glsl/emit_glsl_context_get_set.cpp
index 0c1fbc7b1..282668b36 100644
--- a/src/shader_recompiler/backend/glsl/emit_glsl_context_get_set.cpp
+++ b/src/shader_recompiler/backend/glsl/emit_glsl_context_get_set.cpp
@@ -35,6 +35,15 @@ std::string_view OutputVertexIndex(EmitContext& ctx) {
return ctx.stage == Stage::TessellationControl ? "[gl_InvocationID]" : "";
}
+std::string ChooseCbuf(EmitContext& ctx, const IR::Value& binding, std::string_view index) {
+ if (binding.IsImmediate()) {
+ return fmt::format("{}_cbuf{}[{}]", ctx.stage_name, binding.U32(), index);
+ } else {
+ const auto binding_var{ctx.var_alloc.Consume(binding)};
+ return fmt::format("GetCbufIndirect({},{})", binding_var, index);
+ }
+}
+
void GetCbuf(EmitContext& ctx, std::string_view ret, const IR::Value& binding,
const IR::Value& offset, u32 num_bits, std::string_view cast = {},
std::string_view bit_offset = {}) {
@@ -55,8 +64,8 @@ void GetCbuf(EmitContext& ctx, std::string_view ret, const IR::Value& binding,
const auto swizzle{is_immediate ? fmt::format(".{}", OffsetSwizzle(offset.U32()))
: fmt::format("[({}>>2)%4]", offset_var)};
- const auto cbuf{fmt::format("{}_cbuf{}", ctx.stage_name, binding.U32())};
- const auto cbuf_cast{fmt::format("{}({}[{}]{{}})", cast, cbuf, index)};
+ const auto cbuf{ChooseCbuf(ctx, binding, index)};
+ const auto cbuf_cast{fmt::format("{}({}{{}})", cast, cbuf)};
const auto extraction{num_bits == 32 ? cbuf_cast
: fmt::format("bitfieldExtract({},int({}),{})", cbuf_cast,
bit_offset, num_bits)};
@@ -140,9 +149,9 @@ void EmitGetCbufF32(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
void EmitGetCbufU32x2(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
const IR::Value& offset) {
- const auto cbuf{fmt::format("{}_cbuf{}", ctx.stage_name, binding.U32())};
const auto cast{ctx.profile.has_gl_cbuf_ftou_bug ? "" : "ftou"};
if (offset.IsImmediate()) {
+ const auto cbuf{fmt::format("{}_cbuf{}", ctx.stage_name, binding.U32())};
static constexpr u32 cbuf_size{0x10000};
const u32 u32_offset{offset.U32()};
const s32 signed_offset{static_cast<s32>(offset.U32())};
@@ -162,17 +171,17 @@ void EmitGetCbufU32x2(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding
return;
}
const auto offset_var{ctx.var_alloc.Consume(offset)};
+ const auto cbuf{ChooseCbuf(ctx, binding, fmt::format("{}>>4", offset_var))};
if (!ctx.profile.has_gl_component_indexing_bug) {
- ctx.AddU32x2("{}=uvec2({}({}[{}>>4][({}>>2)%4]),{}({}[({}+4)>>4][(({}+4)>>2)%4]));", inst,
- cast, cbuf, offset_var, offset_var, cast, cbuf, offset_var, offset_var);
+ ctx.AddU32x2("{}=uvec2({}({}[({}>>2)%4]),{}({}[(({}+4)>>2)%4]));", inst, cast, cbuf,
+ offset_var, cast, cbuf, offset_var);
return;
}
const auto ret{ctx.var_alloc.Define(inst, GlslVarType::U32x2)};
const auto cbuf_offset{fmt::format("{}>>2", offset_var)};
for (u32 swizzle = 0; swizzle < 4; ++swizzle) {
- ctx.Add("if(({}&3)=={}){}=uvec2({}({}[{}>>4].{}),{}({}[({}+4)>>4].{}));", cbuf_offset,
- swizzle, ret, cast, cbuf, offset_var, "xyzw"[swizzle], cast, cbuf, offset_var,
- "xyzw"[(swizzle + 1) % 4]);
+ ctx.Add("if(({}&3)=={}){}=uvec2({}({}.{}),{}({}.{}));", cbuf_offset, swizzle, ret, cast,
+ cbuf, "xyzw"[swizzle], cast, cbuf, "xyzw"[(swizzle + 1) % 4]);
}
}
diff --git a/src/shader_recompiler/backend/glsl/glsl_emit_context.cpp b/src/shader_recompiler/backend/glsl/glsl_emit_context.cpp
index e816a93ec..17266f40d 100644
--- a/src/shader_recompiler/backend/glsl/glsl_emit_context.cpp
+++ b/src/shader_recompiler/backend/glsl/glsl_emit_context.cpp
@@ -359,6 +359,7 @@ EmitContext::EmitContext(IR::Program& program, Bindings& bindings, const Profile
header += "layout(location=0) uniform vec4 scaling;";
}
DefineConstantBuffers(bindings);
+ DefineConstantBufferIndirect();
DefineStorageBuffers(bindings);
SetupImages(bindings);
SetupTextures(bindings);
@@ -436,6 +437,24 @@ void EmitContext::DefineConstantBuffers(Bindings& bindings) {
}
}
+void EmitContext::DefineConstantBufferIndirect() {
+ if (!info.uses_cbuf_indirect) {
+ return;
+ }
+
+ header += profile.has_gl_cbuf_ftou_bug ? "uvec4 " : "vec4 ";
+ header += "GetCbufIndirect(uint binding, uint offset){"
+ "switch(binding){"
+ "default:";
+
+ for (const auto& desc : info.constant_buffer_descriptors) {
+ header +=
+ fmt::format("case {}:return {}_cbuf{}[offset];", desc.index, stage_name, desc.index);
+ }
+
+ header += "}}";
+}
+
void EmitContext::DefineStorageBuffers(Bindings& bindings) {
if (info.storage_buffers_descriptors.empty()) {
return;
diff --git a/src/shader_recompiler/backend/glsl/glsl_emit_context.h b/src/shader_recompiler/backend/glsl/glsl_emit_context.h
index d9b639d29..2b13db6e6 100644
--- a/src/shader_recompiler/backend/glsl/glsl_emit_context.h
+++ b/src/shader_recompiler/backend/glsl/glsl_emit_context.h
@@ -162,6 +162,7 @@ public:
private:
void SetupExtensions();
void DefineConstantBuffers(Bindings& bindings);
+ void DefineConstantBufferIndirect();
void DefineStorageBuffers(Bindings& bindings);
void DefineGenericOutput(size_t index, u32 invocations);
void DefineHelperFunctions();
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp
index d3cbb14a9..cb47d253c 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp
@@ -2,6 +2,8 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <bit>
+
#include "shader_recompiler/backend/spirv/emit_spirv.h"
#include "shader_recompiler/backend/spirv/emit_spirv_instructions.h"
#include "shader_recompiler/backend/spirv/spirv_emit_context.h"
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp
index 3c2a5e16f..aa7082978 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp
@@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <bit>
#include <tuple>
#include <utility>
diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
index 53be98ced..9c83cd2e4 100644
--- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
+++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
@@ -4,6 +4,7 @@
#include <algorithm>
#include <array>
+#include <bit>
#include <climits>
#include <boost/container/static_vector.hpp>
@@ -1042,15 +1043,15 @@ void EmitContext::DefineConstantBufferIndirectFunctions(const Info& info) {
const Id merge_label{OpLabel()};
const Id uniform_type{uniform_types.*member_ptr};
- std::array<Id, Info::MAX_CBUFS> buf_labels;
- std::array<Sirit::Literal, Info::MAX_CBUFS> buf_literals;
- for (u32 i = 0; i < Info::MAX_CBUFS; i++) {
+ std::array<Id, Info::MAX_INDIRECT_CBUFS> buf_labels;
+ std::array<Sirit::Literal, Info::MAX_INDIRECT_CBUFS> buf_literals;
+ for (u32 i = 0; i < Info::MAX_INDIRECT_CBUFS; i++) {
buf_labels[i] = OpLabel();
buf_literals[i] = Sirit::Literal{i};
}
OpSelectionMerge(merge_label, spv::SelectionControlMask::MaskNone);
OpSwitch(binding, buf_labels[0], buf_literals, buf_labels);
- for (u32 i = 0; i < Info::MAX_CBUFS; i++) {
+ for (u32 i = 0; i < Info::MAX_INDIRECT_CBUFS; i++) {
AddLabel(buf_labels[i]);
const Id cbuf{cbufs[i].*member_ptr};
const Id access_chain{OpAccessChain(uniform_type, cbuf, u32_zero_value, offset)};
@@ -1063,22 +1064,23 @@ void EmitContext::DefineConstantBufferIndirectFunctions(const Info& info) {
return func;
}};
IR::Type types{info.used_indirect_cbuf_types};
- if (True(types & IR::Type::U8)) {
+ bool supports_aliasing = profile.support_descriptor_aliasing;
+ if (supports_aliasing && True(types & IR::Type::U8)) {
load_const_func_u8 = make_accessor(U8, &UniformDefinitions::U8);
}
- if (True(types & IR::Type::U16)) {
+ if (supports_aliasing && True(types & IR::Type::U16)) {
load_const_func_u16 = make_accessor(U16, &UniformDefinitions::U16);
}
- if (True(types & IR::Type::F32)) {
+ if (supports_aliasing && True(types & IR::Type::F32)) {
load_const_func_f32 = make_accessor(F32[1], &UniformDefinitions::F32);
}
- if (True(types & IR::Type::U32)) {
+ if (supports_aliasing && True(types & IR::Type::U32)) {
load_const_func_u32 = make_accessor(U32[1], &UniformDefinitions::U32);
}
- if (True(types & IR::Type::U32x2)) {
+ if (supports_aliasing && True(types & IR::Type::U32x2)) {
load_const_func_u32x2 = make_accessor(U32[2], &UniformDefinitions::U32x2);
}
- if (True(types & IR::Type::U32x4)) {
+ if (!supports_aliasing || True(types & IR::Type::U32x4)) {
load_const_func_u32x4 = make_accessor(U32[4], &UniformDefinitions::U32x4);
}
}
diff --git a/src/shader_recompiler/frontend/ir/microinstruction.cpp b/src/shader_recompiler/frontend/ir/microinstruction.cpp
index 631446cf7..4a2564f47 100644
--- a/src/shader_recompiler/frontend/ir/microinstruction.cpp
+++ b/src/shader_recompiler/frontend/ir/microinstruction.cpp
@@ -326,6 +326,11 @@ void Inst::AddPhiOperand(Block* predecessor, const Value& value) {
phi_args.emplace_back(predecessor, value);
}
+void Inst::ErasePhiOperand(size_t index) {
+ const auto operand_it{phi_args.begin() + static_cast<ptrdiff_t>(index)};
+ phi_args.erase(operand_it);
+}
+
void Inst::OrderPhiArgs() {
if (op != Opcode::Phi) {
throw LogicError("{} is not a Phi instruction", op);
diff --git a/src/shader_recompiler/frontend/ir/value.h b/src/shader_recompiler/frontend/ir/value.h
index 947579852..14f6e55bc 100644
--- a/src/shader_recompiler/frontend/ir/value.h
+++ b/src/shader_recompiler/frontend/ir/value.h
@@ -179,9 +179,13 @@ public:
/// Get a pointer to the block of a phi argument.
[[nodiscard]] Block* PhiBlock(size_t index) const;
+
/// Add phi operand to a phi instruction.
void AddPhiOperand(Block* predecessor, const Value& value);
+ // Erase the phi operand at the given index.
+ void ErasePhiOperand(size_t index);
+
/// Orders the Phi arguments from farthest away to nearest.
void OrderPhiArgs();
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/exit_program.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/exit_program.cpp
index 6cd13ec48..62d20ebe4 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/exit_program.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/exit_program.cpp
@@ -11,9 +11,13 @@ void ExitFragment(TranslatorVisitor& v) {
const ProgramHeader sph{v.env.SPH()};
IR::Reg src_reg{IR::Reg::R0};
for (u32 render_target = 0; render_target < 8; ++render_target) {
+ if (!sph.ps.HasOutputComponents(render_target)) {
+ continue;
+ }
const std::array<bool, 4> mask{sph.ps.EnabledOutputComponents(render_target)};
for (u32 component = 0; component < 4; ++component) {
if (!mask[component]) {
+ ++src_reg;
continue;
}
v.ir.SetFragColor(render_target, component, v.F(src_reg));
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/texture_load.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/texture_load.cpp
index 57b4f0eee..60732215b 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/texture_load.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/texture_load.cpp
@@ -132,7 +132,7 @@ void Impl(TranslatorVisitor& v, u64 insn, bool is_bindless) {
multisample = v.X(meta_reg++);
}
if (tld.clamp != 0) {
- throw NotImplementedException("TLD.CL - CLAMP is not implmented");
+ throw NotImplementedException("TLD.CL - CLAMP is not implemented");
}
IR::TextureInstInfo info{};
info.type.Assign(GetType(tld.type));
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/texture_mipmap_level.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/texture_mipmap_level.cpp
index 311a9e763..f89ce1b68 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/texture_mipmap_level.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/texture_mipmap_level.cpp
@@ -81,7 +81,7 @@ void Impl(TranslatorVisitor& v, u64 insn, bool is_bindless) {
} const tmml{insn};
if ((tmml.mask & 0b1100) != 0) {
- throw NotImplementedException("TMML BA results are not implmented");
+ throw NotImplementedException("TMML BA results are not implemented");
}
const IR::Value coords{MakeCoords(v, tmml.coord_reg, tmml.type)};
diff --git a/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp b/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp
index 0b2c60842..16278faab 100644
--- a/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp
+++ b/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp
@@ -32,13 +32,8 @@ void AddConstantBufferDescriptor(Info& info, u32 index, u32 count) {
void AddRegisterIndexedLdc(Info& info) {
info.uses_cbuf_indirect = true;
- // The shader can use any possible constant buffer
- info.constant_buffer_mask = (1 << Info::MAX_CBUFS) - 1;
-
- auto& cbufs{info.constant_buffer_descriptors};
- cbufs.clear();
- for (u32 i = 0; i < Info::MAX_CBUFS; i++) {
- cbufs.push_back(ConstantBufferDescriptor{.index = i, .count = 1});
+ for (u32 i = 0; i < Info::MAX_INDIRECT_CBUFS; i++) {
+ AddConstantBufferDescriptor(info, i, 1);
// The shader can use any possible access size
info.constant_buffer_used_sizes[i] = 0x10'000;
diff --git a/src/shader_recompiler/ir_opt/dead_code_elimination_pass.cpp b/src/shader_recompiler/ir_opt/dead_code_elimination_pass.cpp
index 400836301..6697fde85 100644
--- a/src/shader_recompiler/ir_opt/dead_code_elimination_pass.cpp
+++ b/src/shader_recompiler/ir_opt/dead_code_elimination_pass.cpp
@@ -2,24 +2,104 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <algorithm>
+
+#include <boost/container/small_vector.hpp>
+
#include "shader_recompiler/frontend/ir/basic_block.h"
#include "shader_recompiler/frontend/ir/value.h"
#include "shader_recompiler/ir_opt/passes.h"
namespace Shader::Optimization {
-
-void DeadCodeEliminationPass(IR::Program& program) {
+namespace {
+template <bool TEST_USES>
+void DeadInstElimination(IR::Block* const block) {
// We iterate over the instructions in reverse order.
// This is because removing an instruction reduces the number of uses for earlier instructions.
- for (IR::Block* const block : program.post_order_blocks) {
- auto it{block->end()};
- while (it != block->begin()) {
- --it;
- if (!it->HasUses() && !it->MayHaveSideEffects()) {
- it->Invalidate();
- it = block->Instructions().erase(it);
+ auto it{block->end()};
+ while (it != block->begin()) {
+ --it;
+ if constexpr (TEST_USES) {
+ if (it->HasUses() || it->MayHaveSideEffects()) {
+ continue;
+ }
+ }
+ it->Invalidate();
+ it = block->Instructions().erase(it);
+ }
+}
+
+void DeletedPhiArgElimination(IR::Program& program, std::span<const IR::Block*> dead_blocks) {
+ for (IR::Block* const block : program.blocks) {
+ for (IR::Inst& phi : *block) {
+ if (!IR::IsPhi(phi)) {
+ continue;
+ }
+ for (size_t i = 0; i < phi.NumArgs(); ++i) {
+ if (std::ranges::find(dead_blocks, phi.PhiBlock(i)) == dead_blocks.end()) {
+ continue;
+ }
+ // Phi operand at this index is an unreachable block
+ phi.ErasePhiOperand(i);
+ --i;
+ }
+ }
+ }
+}
+
+void DeadBranchElimination(IR::Program& program) {
+ boost::container::small_vector<const IR::Block*, 3> dead_blocks;
+ const auto begin_it{program.syntax_list.begin()};
+ for (auto node_it = begin_it; node_it != program.syntax_list.end(); ++node_it) {
+ if (node_it->type != IR::AbstractSyntaxNode::Type::If) {
+ continue;
+ }
+ IR::Inst* const cond_ref{node_it->data.if_node.cond.Inst()};
+ const IR::U1 cond{cond_ref->Arg(0)};
+ if (!cond.IsImmediate()) {
+ continue;
+ }
+ if (cond.U1()) {
+ continue;
+ }
+ // False immediate condition. Remove condition ref, erase the entire branch.
+ cond_ref->Invalidate();
+ // Account for nested if-statements within the if(false) branch
+ u32 nested_ifs{1u};
+ while (node_it->type != IR::AbstractSyntaxNode::Type::EndIf || nested_ifs > 0) {
+ node_it = program.syntax_list.erase(node_it);
+ switch (node_it->type) {
+ case IR::AbstractSyntaxNode::Type::If:
+ ++nested_ifs;
+ break;
+ case IR::AbstractSyntaxNode::Type::EndIf:
+ --nested_ifs;
+ break;
+ case IR::AbstractSyntaxNode::Type::Block: {
+ IR::Block* const block{node_it->data.block};
+ DeadInstElimination<false>(block);
+ dead_blocks.push_back(block);
+ break;
+ }
+ default:
+ break;
}
}
+ // Erase EndIf node of the if(false) branch
+ node_it = program.syntax_list.erase(node_it);
+ // Account for loop increment
+ --node_it;
+ }
+ if (!dead_blocks.empty()) {
+ DeletedPhiArgElimination(program, std::span(dead_blocks.data(), dead_blocks.size()));
+ }
+}
+} // namespace
+
+void DeadCodeEliminationPass(IR::Program& program) {
+ DeadBranchElimination(program);
+ for (IR::Block* const block : program.post_order_blocks) {
+ DeadInstElimination<true>(block);
}
}
diff --git a/src/shader_recompiler/program_header.h b/src/shader_recompiler/program_header.h
index bd6c2bfb5..0cd6597ef 100644
--- a/src/shader_recompiler/program_header.h
+++ b/src/shader_recompiler/program_header.h
@@ -196,6 +196,11 @@ struct ProgramHeader {
return {(bits & 1) != 0, (bits & 2) != 0, (bits & 4) != 0, (bits & 8) != 0};
}
+ [[nodiscard]] bool HasOutputComponents(u32 rt) const noexcept {
+ const u32 bits{omap.target >> (rt * 4)};
+ return (bits & 0xf) != 0;
+ }
+
[[nodiscard]] std::array<PixelImap, 4> GenericInputMap(u32 attribute) const {
const auto& vector{imap_generic_vector[attribute]};
return {vector.x, vector.y, vector.z, vector.w};
diff --git a/src/shader_recompiler/shader_info.h b/src/shader_recompiler/shader_info.h
index 9d36bd9eb..a3a09c71c 100644
--- a/src/shader_recompiler/shader_info.h
+++ b/src/shader_recompiler/shader_info.h
@@ -105,6 +105,7 @@ struct ImageDescriptor {
using ImageDescriptors = boost::container::small_vector<ImageDescriptor, 4>;
struct Info {
+ static constexpr size_t MAX_INDIRECT_CBUFS{14};
static constexpr size_t MAX_CBUFS{18};
static constexpr size_t MAX_SSBOS{32};
diff --git a/src/video_core/buffer_cache/buffer_base.h b/src/video_core/buffer_cache/buffer_base.h
index be2113f5a..10975884b 100644
--- a/src/video_core/buffer_cache/buffer_base.h
+++ b/src/video_core/buffer_cache/buffer_base.h
@@ -212,7 +212,7 @@ public:
void FlushCachedWrites() noexcept {
flags &= ~BufferFlagBits::CachedWrites;
const u64 num_words = NumWords();
- const u64* const cached_words = Array<Type::CachedCPU>();
+ u64* const cached_words = Array<Type::CachedCPU>();
u64* const untracked_words = Array<Type::Untracked>();
u64* const cpu_words = Array<Type::CPU>();
for (u64 word_index = 0; word_index < num_words; ++word_index) {
@@ -220,6 +220,7 @@ public:
NotifyRasterizer<false>(word_index, untracked_words[word_index], cached_bits);
untracked_words[word_index] |= cached_bits;
cpu_words[word_index] |= cached_bits;
+ cached_words[word_index] = 0;
}
}
diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h
index 200d792dd..3f2bf6294 100644
--- a/src/video_core/buffer_cache/buffer_cache.h
+++ b/src/video_core/buffer_cache/buffer_cache.h
@@ -76,8 +76,9 @@ class BufferCache {
static constexpr BufferId NULL_BUFFER_ID{0};
- static constexpr u64 EXPECTED_MEMORY = 512_MiB;
- static constexpr u64 CRITICAL_MEMORY = 1_GiB;
+ static constexpr s64 DEFAULT_EXPECTED_MEMORY = 512_MiB;
+ static constexpr s64 DEFAULT_CRITICAL_MEMORY = 1_GiB;
+ static constexpr s64 TARGET_THRESHOLD = 4_GiB;
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
@@ -436,6 +437,8 @@ private:
Common::LeastRecentlyUsedCache<LRUItemParams> lru_cache;
u64 frame_tick = 0;
u64 total_used_memory = 0;
+ u64 minimum_memory = 0;
+ u64 critical_memory = 0;
std::array<BufferId, ((1ULL << 39) >> PAGE_BITS)> page_table;
};
@@ -451,11 +454,30 @@ BufferCache<P>::BufferCache(VideoCore::RasterizerInterface& rasterizer_,
// Ensure the first slot is used for the null buffer
void(slot_buffers.insert(runtime, NullBufferParams{}));
common_ranges.clear();
+
+ if (!runtime.CanReportMemoryUsage()) {
+ minimum_memory = DEFAULT_EXPECTED_MEMORY;
+ critical_memory = DEFAULT_CRITICAL_MEMORY;
+ return;
+ }
+
+ const s64 device_memory = static_cast<s64>(runtime.GetDeviceLocalMemory());
+ const s64 min_spacing_expected = device_memory - 1_GiB - 512_MiB;
+ const s64 min_spacing_critical = device_memory - 1_GiB;
+ const s64 mem_threshold = std::min(device_memory, TARGET_THRESHOLD);
+ const s64 min_vacancy_expected = (6 * mem_threshold) / 10;
+ const s64 min_vacancy_critical = (3 * mem_threshold) / 10;
+ minimum_memory = static_cast<u64>(
+ std::max(std::min(device_memory - min_vacancy_expected, min_spacing_expected),
+ DEFAULT_EXPECTED_MEMORY));
+ critical_memory = static_cast<u64>(
+ std::max(std::min(device_memory - min_vacancy_critical, min_spacing_critical),
+ DEFAULT_CRITICAL_MEMORY));
}
template <class P>
void BufferCache<P>::RunGarbageCollector() {
- const bool aggressive_gc = total_used_memory >= CRITICAL_MEMORY;
+ const bool aggressive_gc = total_used_memory >= critical_memory;
const u64 ticks_to_destroy = aggressive_gc ? 60 : 120;
int num_iterations = aggressive_gc ? 64 : 32;
const auto clean_up = [this, &num_iterations](BufferId buffer_id) {
@@ -486,7 +508,11 @@ void BufferCache<P>::TickFrame() {
const bool skip_preferred = hits * 256 < shots * 251;
uniform_buffer_skip_cache_size = skip_preferred ? DEFAULT_SKIP_CACHE_SIZE : 0;
- if (total_used_memory >= EXPECTED_MEMORY) {
+ // If we can obtain the memory info, use it instead of the estimate.
+ if (runtime.CanReportMemoryUsage()) {
+ total_used_memory = runtime.GetDeviceMemoryUsage();
+ }
+ if (total_used_memory >= minimum_memory) {
RunGarbageCollector();
}
++frame_tick;
@@ -1285,7 +1311,20 @@ void BufferCache<P>::UpdateVertexBuffer(u32 index) {
const GPUVAddr gpu_addr_begin = array.StartAddress();
const GPUVAddr gpu_addr_end = limit.LimitAddress() + 1;
const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr_begin);
- const u32 address_size = static_cast<u32>(gpu_addr_end - gpu_addr_begin);
+ u32 address_size = static_cast<u32>(gpu_addr_end - gpu_addr_begin);
+ if (address_size >= 64_MiB) {
+ // Reported vertex buffer size is very large, cap to mapped buffer size
+ GPUVAddr submapped_addr_end = gpu_addr_begin;
+
+ const auto ranges{gpu_memory.GetSubmappedRange(gpu_addr_begin, address_size)};
+ if (ranges.size() > 0) {
+ const auto& [addr, size] = *ranges.begin();
+ submapped_addr_end = addr + size;
+ }
+
+ address_size =
+ std::min(address_size, static_cast<u32>(submapped_addr_end - gpu_addr_begin));
+ }
const u32 size = address_size; // TODO: Analyze stride and number of vertices
if (array.enable == 0 || size == 0 || !cpu_addr) {
vertex_buffers[index] = NULL_BINDING;
diff --git a/src/video_core/command_classes/codecs/codec.cpp b/src/video_core/command_classes/codecs/codec.cpp
index 81fac94bf..40f7755e8 100644
--- a/src/video_core/command_classes/codecs/codec.cpp
+++ b/src/video_core/command_classes/codecs/codec.cpp
@@ -56,6 +56,18 @@ AVPixelFormat GetGpuFormat(AVCodecContext* av_codec_ctx, const AVPixelFormat* pi
av_codec_ctx->pix_fmt = PREFERRED_CPU_FMT;
return PREFERRED_CPU_FMT;
}
+
+// List all the currently available hwcontext in ffmpeg
+std::vector<AVHWDeviceType> ListSupportedContexts() {
+ std::vector<AVHWDeviceType> contexts{};
+ AVHWDeviceType current_device_type = AV_HWDEVICE_TYPE_NONE;
+ do {
+ current_device_type = av_hwdevice_iterate_types(current_device_type);
+ contexts.push_back(current_device_type);
+ } while (current_device_type != AV_HWDEVICE_TYPE_NONE);
+ return contexts;
+}
+
} // namespace
void AVFrameDeleter(AVFrame* ptr) {
@@ -76,17 +88,6 @@ Codec::~Codec() {
av_buffer_unref(&av_gpu_decoder);
}
-// List all the currently available hwcontext in ffmpeg
-static std::vector<AVHWDeviceType> ListSupportedContexts() {
- std::vector<AVHWDeviceType> contexts{};
- AVHWDeviceType current_device_type = AV_HWDEVICE_TYPE_NONE;
- do {
- current_device_type = av_hwdevice_iterate_types(current_device_type);
- contexts.push_back(current_device_type);
- } while (current_device_type != AV_HWDEVICE_TYPE_NONE);
- return contexts;
-}
-
bool Codec::CreateGpuAvDevice() {
static constexpr auto HW_CONFIG_METHOD = AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX;
static const auto supported_contexts = ListSupportedContexts();
@@ -96,6 +97,8 @@ bool Codec::CreateGpuAvDevice() {
LOG_DEBUG(Service_NVDRV, "{} explicitly unsupported", av_hwdevice_get_type_name(type));
continue;
}
+ // Avoid memory leak from not cleaning up after av_hwdevice_ctx_create
+ av_buffer_unref(&av_gpu_decoder);
const int hwdevice_res = av_hwdevice_ctx_create(&av_gpu_decoder, type, nullptr, nullptr, 0);
if (hwdevice_res < 0) {
LOG_DEBUG(Service_NVDRV, "{} av_hwdevice_ctx_create failed {}",
@@ -127,15 +130,19 @@ bool Codec::CreateGpuAvDevice() {
av_codec->name, av_hwdevice_get_type_name(type));
break;
}
- if (config->methods & HW_CONFIG_METHOD && config->device_type == type) {
- av_codec_ctx->pix_fmt = config->pix_fmt;
- if (config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX) {
+ if ((config->methods & HW_CONFIG_METHOD) != 0 && config->device_type == type) {
+#if defined(__unix__)
+ // Some linux decoding backends are reported to crash with this config method
+ // TODO(ameerj): Properly support this method
+ if ((config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX) != 0) {
// skip zero-copy decoders, we don't currently support them
LOG_DEBUG(Service_NVDRV, "Skipping decoder {} with unsupported capability {}.",
av_hwdevice_get_type_name(type), config->methods);
continue;
}
+#endif
LOG_INFO(Service_NVDRV, "Using {} GPU decoder", av_hwdevice_get_type_name(type));
+ av_codec_ctx->pix_fmt = config->pix_fmt;
return true;
}
}
diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp
index 54a902f56..7399e760f 100644
--- a/src/video_core/engines/maxwell_3d.cpp
+++ b/src/video_core/engines/maxwell_3d.cpp
@@ -214,6 +214,11 @@ void Maxwell3D::ProcessMethodCall(u32 method, u32 argument, u32 nonshadow_argume
regs.index_array.first = regs.small_index.first;
dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
return DrawArrays();
+ case MAXWELL3D_REG_INDEX(small_index_2):
+ regs.index_array.count = regs.small_index_2.count;
+ regs.index_array.first = regs.small_index_2.first;
+ dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
+ return DrawArrays();
case MAXWELL3D_REG_INDEX(topology_override):
use_topology_override = true;
return;
diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h
index 3f5b38e55..d36dc3daa 100644
--- a/src/video_core/engines/maxwell_3d.h
+++ b/src/video_core/engines/maxwell_3d.h
@@ -1220,7 +1220,12 @@ public:
BitField<16, 16, u32> count;
} small_index;
- INSERT_PADDING_WORDS_NOINIT(0x6);
+ union {
+ BitField<0, 16, u32> first;
+ BitField<16, 16, u32> count;
+ } small_index_2;
+
+ INSERT_PADDING_WORDS_NOINIT(0x5);
INSERT_PADDING_WORDS_NOINIT(0x1F);
diff --git a/src/video_core/framebuffer_config.h b/src/video_core/framebuffer_config.h
index b1d455e30..93349bb78 100644
--- a/src/video_core/framebuffer_config.h
+++ b/src/video_core/framebuffer_config.h
@@ -6,41 +6,22 @@
#include "common/common_types.h"
#include "common/math_util.h"
+#include "core/hle/service/nvflinger/buffer_transform_flags.h"
+#include "core/hle/service/nvflinger/pixel_format.h"
namespace Tegra {
+
/**
* Struct describing framebuffer configuration
*/
struct FramebufferConfig {
- enum class PixelFormat : u32 {
- A8B8G8R8_UNORM = 1,
- RGB565_UNORM = 4,
- B8G8R8A8_UNORM = 5,
- };
-
- enum class TransformFlags : 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)
- FlipV = 0x02,
- /// Rotate source image 90 degrees clockwise
- Rotate90 = 0x04,
- /// Rotate source image 180 degrees
- Rotate180 = 0x03,
- /// Rotate source image 270 degrees clockwise
- Rotate270 = 0x07,
- };
-
VAddr address{};
u32 offset{};
u32 width{};
u32 height{};
u32 stride{};
- PixelFormat pixel_format{};
-
- TransformFlags transform_flags{};
+ Service::android::PixelFormat pixel_format{};
+ Service::android::BufferTransformFlags transform_flags{};
Common::Rectangle<int> crop_rect;
};
diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp
index ba9ba082f..789af452d 100644
--- a/src/video_core/gpu.cpp
+++ b/src/video_core/gpu.cpp
@@ -230,7 +230,7 @@ struct GPU::Impl {
void IncrementSyncPoint(u32 syncpoint_id) {
auto& syncpoint = syncpoints.at(syncpoint_id);
syncpoint++;
- std::lock_guard lock{sync_mutex};
+ std::scoped_lock lock{sync_mutex};
sync_cv.notify_all();
auto& interrupt = syncpt_interrupts.at(syncpoint_id);
if (!interrupt.empty()) {
@@ -252,7 +252,7 @@ struct GPU::Impl {
}
void RegisterSyncptInterrupt(u32 syncpoint_id, u32 value) {
- std::lock_guard lock{sync_mutex};
+ std::scoped_lock lock{sync_mutex};
auto& interrupt = syncpt_interrupts.at(syncpoint_id);
bool contains = std::any_of(interrupt.begin(), interrupt.end(),
[value](u32 in_value) { return in_value == value; });
@@ -263,7 +263,7 @@ struct GPU::Impl {
}
[[nodiscard]] bool CancelSyncptInterrupt(u32 syncpoint_id, u32 value) {
- std::lock_guard lock{sync_mutex};
+ std::scoped_lock lock{sync_mutex};
auto& interrupt = syncpt_interrupts.at(syncpoint_id);
const auto iter =
std::find_if(interrupt.begin(), interrupt.end(),
diff --git a/src/video_core/gpu.h b/src/video_core/gpu.h
index 26b8ea233..97c029140 100644
--- a/src/video_core/gpu.h
+++ b/src/video_core/gpu.h
@@ -8,6 +8,7 @@
#include "common/bit_field.h"
#include "common/common_types.h"
+#include "core/hle/service/nvdrv/nvdata.h"
#include "video_core/cdma_pusher.h"
#include "video_core/framebuffer_config.h"
diff --git a/src/video_core/gpu_thread.cpp b/src/video_core/gpu_thread.cpp
index 9547f277a..4e8999915 100644
--- a/src/video_core/gpu_thread.cpp
+++ b/src/video_core/gpu_thread.cpp
@@ -56,7 +56,7 @@ static void RunThread(std::stop_token stop_token, Core::System& system,
if (next.block) {
// We have to lock the write_lock to ensure that the condition_variable wait not get a
// race between the check and the lock itself.
- std::lock_guard lk(state.write_lock);
+ std::scoped_lock lk{state.write_lock};
state.cv.notify_all();
}
}
diff --git a/src/video_core/host_shaders/CMakeLists.txt b/src/video_core/host_shaders/CMakeLists.txt
index af05d47d1..190fc6aea 100644
--- a/src/video_core/host_shaders/CMakeLists.txt
+++ b/src/video_core/host_shaders/CMakeLists.txt
@@ -18,6 +18,7 @@ set(SHADER_FILES
full_screen_triangle.vert
fxaa.frag
fxaa.vert
+ opengl_convert_s8d24.comp
opengl_copy_bc4.comp
opengl_present.frag
opengl_present.vert
diff --git a/src/video_core/host_shaders/opengl_convert_s8d24.comp b/src/video_core/host_shaders/opengl_convert_s8d24.comp
new file mode 100644
index 000000000..83e1ab176
--- /dev/null
+++ b/src/video_core/host_shaders/opengl_convert_s8d24.comp
@@ -0,0 +1,18 @@
+// Copyright 2022 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#version 430 core
+
+layout(local_size_x = 16, local_size_y = 8) in;
+
+layout(binding = 0, rgba8ui) restrict uniform uimage2D destination;
+layout(location = 0) uniform uvec3 size;
+
+void main() {
+ if (any(greaterThanEqual(gl_GlobalInvocationID, size))) {
+ return;
+ }
+ uvec4 components = imageLoad(destination, ivec2(gl_GlobalInvocationID.xy));
+ imageStore(destination, ivec2(gl_GlobalInvocationID.xy), components.wxyz);
+}
diff --git a/src/video_core/renderer_base.cpp b/src/video_core/renderer_base.cpp
index c9627cffd..9756a81d6 100644
--- a/src/video_core/renderer_base.cpp
+++ b/src/video_core/renderer_base.cpp
@@ -26,6 +26,10 @@ void RendererBase::UpdateCurrentFramebufferLayout() {
render_window.UpdateCurrentFramebufferLayout(layout.width, layout.height);
}
+bool RendererBase::IsScreenshotPending() const {
+ return renderer_settings.screenshot_requested;
+}
+
void RendererBase::RequestScreenshot(void* data, std::function<void(bool)> callback,
const Layout::FramebufferLayout& layout) {
if (renderer_settings.screenshot_requested) {
diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h
index c5f974080..30d19b178 100644
--- a/src/video_core/renderer_base.h
+++ b/src/video_core/renderer_base.h
@@ -83,6 +83,9 @@ public:
/// Refreshes the settings common to all renderers
void RefreshBaseSettings();
+ /// Returns true if a screenshot is being processed
+ bool IsScreenshotPending() const;
+
/// Request a screenshot of the next frame
void RequestScreenshot(void* data, std::function<void(bool)> callback,
const Layout::FramebufferLayout& layout);
diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.cpp b/src/video_core/renderer_opengl/gl_buffer_cache.cpp
index d4dd10bb6..f1f7b384b 100644
--- a/src/video_core/renderer_opengl/gl_buffer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_buffer_cache.cpp
@@ -135,6 +135,20 @@ BufferCacheRuntime::BufferCacheRuntime(const Device& device_)
buffer.Create();
glNamedBufferData(buffer.handle, 0x10'000, nullptr, GL_STREAM_COPY);
}
+
+ device_access_memory = [this]() -> u64 {
+ if (device.CanReportMemoryUsage()) {
+ return device.GetCurrentDedicatedVideoMemory() + 512_MiB;
+ }
+ return 2_GiB; // Return minimum requirements
+ }();
+}
+
+u64 BufferCacheRuntime::GetDeviceMemoryUsage() const {
+ if (device.CanReportMemoryUsage()) {
+ return device_access_memory - device.GetCurrentDedicatedVideoMemory();
+ }
+ return 2_GiB;
}
void BufferCacheRuntime::CopyBuffer(Buffer& dst_buffer, Buffer& src_buffer,
diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.h b/src/video_core/renderer_opengl/gl_buffer_cache.h
index 7287731b6..a8699f28c 100644
--- a/src/video_core/renderer_opengl/gl_buffer_cache.h
+++ b/src/video_core/renderer_opengl/gl_buffer_cache.h
@@ -89,6 +89,8 @@ public:
void BindImageBuffer(Buffer& buffer, u32 offset, u32 size,
VideoCore::Surface::PixelFormat format);
+ u64 GetDeviceMemoryUsage() const;
+
void BindFastUniformBuffer(size_t stage, u32 binding_index, u32 size) {
const GLuint handle = fast_uniforms[stage][binding_index].handle;
const GLsizeiptr gl_size = static_cast<GLsizeiptr>(size);
@@ -151,6 +153,14 @@ public:
use_storage_buffers = use_storage_buffers_;
}
+ u64 GetDeviceLocalMemory() const {
+ return device_access_memory;
+ }
+
+ bool CanReportMemoryUsage() const {
+ return device.CanReportMemoryUsage();
+ }
+
private:
static constexpr std::array PABO_LUT{
GL_VERTEX_PROGRAM_PARAMETER_BUFFER_NV, GL_TESS_CONTROL_PROGRAM_PARAMETER_BUFFER_NV,
@@ -184,6 +194,8 @@ private:
std::array<OGLBuffer, VideoCommon::NUM_COMPUTE_UNIFORM_BUFFERS> copy_compute_uniforms;
u32 index_buffer_offset = 0;
+
+ u64 device_access_memory;
};
struct BufferCacheParams {
diff --git a/src/video_core/renderer_opengl/gl_device.cpp b/src/video_core/renderer_opengl/gl_device.cpp
index 715cd3a48..597301eeb 100644
--- a/src/video_core/renderer_opengl/gl_device.cpp
+++ b/src/video_core/renderer_opengl/gl_device.cpp
@@ -13,12 +13,15 @@
#include <glad/glad.h>
+#include "common/literals.h"
#include "common/logging/log.h"
#include "common/settings.h"
#include "shader_recompiler/stage.h"
#include "video_core/renderer_opengl/gl_device.h"
#include "video_core/renderer_opengl/gl_resource_manager.h"
+using namespace Common::Literals;
+
namespace OpenGL {
namespace {
constexpr std::array LIMIT_UBOS = {
@@ -165,6 +168,7 @@ Device::Device() {
has_sparse_texture_2 = GLAD_GL_ARB_sparse_texture2;
warp_size_potentially_larger_than_guest = !is_nvidia && !is_intel;
need_fastmath_off = is_nvidia;
+ can_report_memory = GLAD_GL_NVX_gpu_memory_info;
// At the moment of writing this, only Nvidia's driver optimizes BufferSubData on exclusive
// uniform buffers as "push constants"
@@ -276,4 +280,10 @@ void main() {
})");
}
+u64 Device::GetCurrentDedicatedVideoMemory() const {
+ GLint cur_avail_mem_kb = 0;
+ glGetIntegerv(GL_GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX, &cur_avail_mem_kb);
+ return static_cast<u64>(cur_avail_mem_kb) * 1_KiB;
+}
+
} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_device.h b/src/video_core/renderer_opengl/gl_device.h
index 95c2e8d38..9bb0b9148 100644
--- a/src/video_core/renderer_opengl/gl_device.h
+++ b/src/video_core/renderer_opengl/gl_device.h
@@ -20,6 +20,8 @@ public:
[[nodiscard]] std::string GetVendorName() const;
+ u64 GetCurrentDedicatedVideoMemory() const;
+
u32 GetMaxUniformBuffers(Shader::Stage stage) const noexcept {
return max_uniform_buffers[static_cast<size_t>(stage)];
}
@@ -168,6 +170,10 @@ public:
return vendor_name == "ATI Technologies Inc.";
}
+ bool CanReportMemoryUsage() const {
+ return can_report_memory;
+ }
+
private:
static bool TestVariableAoffi();
static bool TestPreciseBug();
@@ -210,6 +216,7 @@ private:
bool need_fastmath_off{};
bool has_cbuf_ftou_bug{};
bool has_bool_ref_bug{};
+ bool can_report_memory{};
std::string vendor_name;
};
diff --git a/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp b/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp
index 9e6732abd..fd40966d5 100644
--- a/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp
+++ b/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp
@@ -253,7 +253,7 @@ GraphicsPipeline::GraphicsPipeline(
}
}
if (in_parallel) {
- std::lock_guard lock{built_mutex};
+ std::scoped_lock lock{built_mutex};
built_fence.Create();
// Flush this context to ensure compilation commands and fence are in the GPU pipe.
glFlush();
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 4d632d211..8ef79753f 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -15,8 +15,9 @@
#include "common/logging/log.h"
#include "common/math_util.h"
#include "common/microprofile.h"
+#include "common/scope_exit.h"
#include "common/settings.h"
-#include "core/memory.h"
+
#include "video_core/engines/kepler_compute.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/memory_manager.h"
@@ -210,6 +211,7 @@ void RasterizerOpenGL::Clear() {
void RasterizerOpenGL::Draw(bool is_indexed, bool is_instanced) {
MICROPROFILE_SCOPE(OpenGL_Drawing);
+ SCOPE_EXIT({ gpu.TickWork(); });
query_cache.UpdateCounters();
GraphicsPipeline* const pipeline{shader_cache.CurrentGraphicsPipeline()};
@@ -265,8 +267,6 @@ void RasterizerOpenGL::Draw(bool is_indexed, bool is_instanced) {
++num_queued_commands;
has_written_global_memory |= pipeline->WritesGlobalMemory();
-
- gpu.TickWork();
}
void RasterizerOpenGL::DispatchCompute() {
@@ -520,6 +520,8 @@ bool RasterizerOpenGL::AccelerateDisplay(const Tegra::FramebufferConfig& config,
// ASSERT_MSG(image_view->size.width == config.width, "Framebuffer width is different");
// ASSERT_MSG(image_view->size.height == config.height, "Framebuffer height is different");
+ screen_info.texture.width = image_view->size.width;
+ screen_info.texture.height = image_view->size.height;
screen_info.display_texture = image_view->Handle(Shader::TextureType::Color2D);
screen_info.display_srgb = VideoCore::Surface::IsPixelFormatSRGB(image_view->format);
return true;
@@ -557,12 +559,19 @@ void RasterizerOpenGL::SyncViewport() {
const bool dirty_viewport = flags[Dirty::Viewports] || rescale_viewports;
const bool dirty_clip_control = flags[Dirty::ClipControl];
- if (dirty_clip_control || flags[Dirty::FrontFace]) {
+ if (dirty_viewport || dirty_clip_control || flags[Dirty::FrontFace]) {
flags[Dirty::FrontFace] = false;
GLenum mode = MaxwellToGL::FrontFace(regs.front_face);
+ bool flip_faces = false;
if (regs.screen_y_control.triangle_rast_flip != 0 &&
regs.viewport_transform[0].scale_y < 0.0f) {
+ flip_faces = !flip_faces;
+ }
+ if (regs.viewport_transform[0].scale_z < 0.0f) {
+ flip_faces = !flip_faces;
+ }
+ if (flip_faces) {
switch (mode) {
case GL_CW:
mode = GL_CCW;
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp
index 6423992c3..05c5e702c 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp
@@ -258,7 +258,7 @@ void ShaderCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading,
[this, key, env = std::move(env), &state, &callback](Context* ctx) mutable {
ctx->pools.ReleaseContents();
auto pipeline{CreateComputePipeline(ctx->pools, key, env)};
- std::lock_guard lock{state.mutex};
+ std::scoped_lock lock{state.mutex};
if (pipeline) {
compute_cache.emplace(key, std::move(pipeline));
}
@@ -280,7 +280,7 @@ void ShaderCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading,
}
ctx->pools.ReleaseContents();
auto pipeline{CreateGraphicsPipeline(ctx->pools, key, MakeSpan(env_ptrs), false)};
- std::lock_guard lock{state.mutex};
+ std::scoped_lock lock{state.mutex};
if (pipeline) {
graphics_cache.emplace(key, std::move(pipeline));
}
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp
index 3c1f79a27..f8c6e5c7e 100644
--- a/src/video_core/renderer_opengl/gl_texture_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp
@@ -182,6 +182,26 @@ GLenum AttachmentType(PixelFormat format) {
}
}
+GLint ConvertA5B5G5R1_UNORM(SwizzleSource source) {
+ switch (source) {
+ case SwizzleSource::Zero:
+ return GL_ZERO;
+ case SwizzleSource::R:
+ return GL_ALPHA;
+ case SwizzleSource::G:
+ return GL_BLUE;
+ case SwizzleSource::B:
+ return GL_GREEN;
+ case SwizzleSource::A:
+ return GL_RED;
+ case SwizzleSource::OneInt:
+ case SwizzleSource::OneFloat:
+ return GL_ONE;
+ }
+ UNREACHABLE_MSG("Invalid swizzle source={}", source);
+ return GL_NONE;
+}
+
void ApplySwizzle(GLuint handle, PixelFormat format, std::array<SwizzleSource, 4> swizzle) {
switch (format) {
case PixelFormat::D24_UNORM_S8_UINT:
@@ -192,6 +212,12 @@ void ApplySwizzle(GLuint handle, PixelFormat format, std::array<SwizzleSource, 4
TextureMode(format, swizzle[0] == SwizzleSource::R));
std::ranges::transform(swizzle, swizzle.begin(), ConvertGreenRed);
break;
+ case PixelFormat::A5B5G5R1_UNORM: {
+ std::array<GLint, 4> gl_swizzle;
+ std::ranges::transform(swizzle, gl_swizzle.begin(), ConvertA5B5G5R1_UNORM);
+ glTextureParameteriv(handle, GL_TEXTURE_SWIZZLE_RGBA, gl_swizzle.data());
+ return;
+ }
default:
break;
}
@@ -409,8 +435,8 @@ ImageBufferMap::~ImageBufferMap() {
TextureCacheRuntime::TextureCacheRuntime(const Device& device_, ProgramManager& program_manager,
StateTracker& state_tracker_)
- : device{device_}, state_tracker{state_tracker_},
- util_shaders(program_manager), resolution{Settings::values.resolution_info} {
+ : device{device_}, state_tracker{state_tracker_}, util_shaders(program_manager),
+ format_conversion_pass{util_shaders}, resolution{Settings::values.resolution_info} {
static constexpr std::array TARGETS{GL_TEXTURE_1D_ARRAY, GL_TEXTURE_2D_ARRAY, GL_TEXTURE_3D};
for (size_t i = 0; i < TARGETS.size(); ++i) {
const GLenum target = TARGETS[i];
@@ -484,6 +510,13 @@ TextureCacheRuntime::TextureCacheRuntime(const Device& device_, ProgramManager&
rescale_read_fbos[i].Create();
}
}
+
+ device_access_memory = [this]() -> u64 {
+ if (device.CanReportMemoryUsage()) {
+ return device.GetCurrentDedicatedVideoMemory() + 512_MiB;
+ }
+ return 2_GiB; // Return minimum requirements
+ }();
}
TextureCacheRuntime::~TextureCacheRuntime() = default;
@@ -500,13 +533,11 @@ ImageBufferMap TextureCacheRuntime::DownloadStagingBuffer(size_t size) {
return download_buffers.RequestMap(size, false);
}
-u64 TextureCacheRuntime::GetDeviceLocalMemory() const {
- if (GLAD_GL_NVX_gpu_memory_info) {
- GLint cur_avail_mem_kb = 0;
- glGetIntegerv(GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX, &cur_avail_mem_kb);
- return static_cast<u64>(cur_avail_mem_kb) * 1_KiB;
+u64 TextureCacheRuntime::GetDeviceMemoryUsage() const {
+ if (device.CanReportMemoryUsage()) {
+ return device_access_memory - device.GetCurrentDedicatedVideoMemory();
}
- return 2_GiB; // Return minimum requirements
+ return 2_GiB;
}
void TextureCacheRuntime::CopyImage(Image& dst_image, Image& src_image,
@@ -686,6 +717,7 @@ Image::Image(TextureCacheRuntime& runtime_, const VideoCommon::ImageInfo& info_,
}
if (IsConverted(runtime->device, info.format, info.type)) {
flags |= ImageFlagBits::Converted;
+ flags |= ImageFlagBits::CostlyLoad;
gl_internal_format = IsPixelFormatSRGB(info.format) ? GL_SRGB8_ALPHA8 : GL_RGBA8;
gl_format = GL_RGBA;
gl_type = GL_UNSIGNED_INT_8_8_8_8_REV;
@@ -1319,6 +1351,9 @@ Framebuffer::Framebuffer(TextureCacheRuntime& runtime, std::span<ImageView*, NUM
Framebuffer::~Framebuffer() = default;
+FormatConversionPass::FormatConversionPass(UtilShaders& util_shaders_)
+ : util_shaders{util_shaders_} {}
+
void FormatConversionPass::ConvertImage(Image& dst_image, Image& src_image,
std::span<const VideoCommon::ImageCopy> copies) {
const GLenum dst_target = ImageTarget(dst_image.info);
@@ -1351,6 +1386,12 @@ void FormatConversionPass::ConvertImage(Image& dst_image, Image& src_image,
dst_origin.z, region.width, region.height, region.depth,
dst_image.GlFormat(), dst_image.GlType(), nullptr);
}
+
+ // Swap component order of S8D24 to ABGR8 reinterprets
+ if (src_image.info.format == PixelFormat::D24_UNORM_S8_UINT &&
+ dst_image.info.format == PixelFormat::A8B8G8R8_UNORM) {
+ util_shaders.ConvertS8D24(dst_image, copies);
+ }
}
} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.h b/src/video_core/renderer_opengl/gl_texture_cache.h
index 7f425631f..672fa8dde 100644
--- a/src/video_core/renderer_opengl/gl_texture_cache.h
+++ b/src/video_core/renderer_opengl/gl_texture_cache.h
@@ -10,6 +10,7 @@
#include <glad/glad.h>
#include "shader_recompiler/shader_info.h"
+#include "video_core/renderer_opengl/gl_device.h"
#include "video_core/renderer_opengl/gl_resource_manager.h"
#include "video_core/renderer_opengl/util_shaders.h"
#include "video_core/texture_cache/image_view_base.h"
@@ -21,7 +22,6 @@ struct ResolutionScalingInfo;
namespace OpenGL {
-class Device;
class ProgramManager;
class StateTracker;
@@ -55,13 +55,14 @@ struct FormatProperties {
class FormatConversionPass {
public:
- FormatConversionPass() = default;
+ explicit FormatConversionPass(UtilShaders& util_shaders);
~FormatConversionPass() = default;
void ConvertImage(Image& dst_image, Image& src_image,
std::span<const VideoCommon::ImageCopy> copies);
private:
+ UtilShaders& util_shaders;
OGLBuffer intermediate_pbo;
size_t pbo_size{};
};
@@ -83,7 +84,15 @@ public:
ImageBufferMap DownloadStagingBuffer(size_t size);
- u64 GetDeviceLocalMemory() const;
+ u64 GetDeviceLocalMemory() const {
+ return device_access_memory;
+ }
+
+ u64 GetDeviceMemoryUsage() const;
+
+ bool CanReportMemoryUsage() const {
+ return device.CanReportMemoryUsage();
+ }
bool ShouldReinterpret([[maybe_unused]] Image& dst, [[maybe_unused]] Image& src) {
return true;
@@ -172,6 +181,7 @@ private:
std::array<OGLFramebuffer, 4> rescale_draw_fbos;
std::array<OGLFramebuffer, 4> rescale_read_fbos;
const Settings::ResolutionScalingInfo& resolution;
+ u64 device_access_memory;
};
class Image : public VideoCommon::ImageBase {
diff --git a/src/video_core/renderer_opengl/maxwell_to_gl.h b/src/video_core/renderer_opengl/maxwell_to_gl.h
index db5bf1d30..03adf3d4c 100644
--- a/src/video_core/renderer_opengl/maxwell_to_gl.h
+++ b/src/video_core/renderer_opengl/maxwell_to_gl.h
@@ -30,6 +30,7 @@ constexpr std::array<FormatTuple, VideoCore::Surface::MaxPixelFormat> FORMAT_TAB
{GL_RGB10_A2, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV}, // A2B10G10R10_UNORM
{GL_RGB10_A2UI, GL_RGBA_INTEGER, GL_UNSIGNED_INT_2_10_10_10_REV}, // A2B10G10R10_UINT
{GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV}, // A1B5G5R5_UNORM
+ {GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1}, // A5B5G5R1_UNORM
{GL_R8, GL_RED, GL_UNSIGNED_BYTE}, // R8_UNORM
{GL_R8_SNORM, GL_RED, GL_BYTE}, // R8_SNORM
{GL_R8I, GL_RED_INTEGER, GL_BYTE}, // R8_SINT
@@ -87,6 +88,7 @@ constexpr std::array<FormatTuple, VideoCore::Surface::MaxPixelFormat> FORMAT_TAB
{GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT}, // BC3_SRGB
{GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM}, // BC7_SRGB
{GL_RGBA4, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4_REV}, // A4B4G4R4_UNORM
+ {GL_R8, GL_RED, GL_UNSIGNED_BYTE}, // R4G4_UNORM
{GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR}, // ASTC_2D_4X4_SRGB
{GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR}, // ASTC_2D_8X8_SRGB
{GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR}, // ASTC_2D_8X5_SRGB
diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp
index 795c97831..3a3c213bb 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.cpp
+++ b/src/video_core/renderer_opengl/renderer_opengl.cpp
@@ -208,6 +208,8 @@ void RendererOpenGL::LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuf
// Framebuffer orientation handling
framebuffer_transform_flags = framebuffer.transform_flags;
framebuffer_crop_rect = framebuffer.crop_rect;
+ framebuffer_width = framebuffer.width;
+ framebuffer_height = framebuffer.height;
const VAddr framebuffer_addr{framebuffer.address + framebuffer.offset};
screen_info.was_accelerated =
@@ -323,12 +325,12 @@ void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture,
GLint internal_format;
switch (framebuffer.pixel_format) {
- case Tegra::FramebufferConfig::PixelFormat::A8B8G8R8_UNORM:
+ case Service::android::PixelFormat::Rgba8888:
internal_format = GL_RGBA8;
texture.gl_format = GL_RGBA;
texture.gl_type = GL_UNSIGNED_INT_8_8_8_8_REV;
break;
- case Tegra::FramebufferConfig::PixelFormat::RGB565_UNORM:
+ case Service::android::PixelFormat::Rgb565:
internal_format = GL_RGB565;
texture.gl_format = GL_RGB;
texture.gl_type = GL_UNSIGNED_SHORT_5_6_5;
@@ -464,8 +466,8 @@ void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) {
const auto& texcoords = screen_info.display_texcoords;
auto left = texcoords.left;
auto right = texcoords.right;
- if (framebuffer_transform_flags != Tegra::FramebufferConfig::TransformFlags::Unset) {
- if (framebuffer_transform_flags == Tegra::FramebufferConfig::TransformFlags::FlipV) {
+ if (framebuffer_transform_flags != Service::android::BufferTransformFlags::Unset) {
+ if (framebuffer_transform_flags == Service::android::BufferTransformFlags::FlipV) {
// Flip the framebuffer vertically
left = texcoords.right;
right = texcoords.left;
@@ -480,9 +482,12 @@ void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) {
ASSERT_MSG(framebuffer_crop_rect.top == 0, "Unimplemented");
ASSERT_MSG(framebuffer_crop_rect.left == 0, "Unimplemented");
+ f32 scale_u = static_cast<f32>(framebuffer_width) / static_cast<f32>(screen_info.texture.width);
+ f32 scale_v =
+ static_cast<f32>(framebuffer_height) / static_cast<f32>(screen_info.texture.height);
+
// Scale the output by the crop width/height. This is commonly used with 1280x720 rendering
// (e.g. handheld mode) on a 1920x1080 framebuffer.
- f32 scale_u = 1.f, scale_v = 1.f;
if (framebuffer_crop_rect.GetWidth() > 0) {
scale_u = static_cast<f32>(framebuffer_crop_rect.GetWidth()) /
static_cast<f32>(screen_info.texture.width);
diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h
index 35706cf05..ae9558a33 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.h
+++ b/src/video_core/renderer_opengl/renderer_opengl.h
@@ -46,7 +46,7 @@ struct TextureInfo {
GLsizei height;
GLenum gl_format;
GLenum gl_type;
- Tegra::FramebufferConfig::PixelFormat pixel_format;
+ Service::android::PixelFormat pixel_format;
};
/// Structure used for storing information about the display target for the Switch screen
@@ -135,8 +135,10 @@ private:
std::vector<u8> gl_framebuffer_data;
/// Used for transforming the framebuffer orientation
- Tegra::FramebufferConfig::TransformFlags framebuffer_transform_flags{};
+ Service::android::BufferTransformFlags framebuffer_transform_flags{};
Common::Rectangle<int> framebuffer_crop_rect;
+ u32 framebuffer_width;
+ u32 framebuffer_height;
};
} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/util_shaders.cpp b/src/video_core/renderer_opengl/util_shaders.cpp
index 897c380b3..04c482a09 100644
--- a/src/video_core/renderer_opengl/util_shaders.cpp
+++ b/src/video_core/renderer_opengl/util_shaders.cpp
@@ -13,6 +13,7 @@
#include "video_core/host_shaders/astc_decoder_comp.h"
#include "video_core/host_shaders/block_linear_unswizzle_2d_comp.h"
#include "video_core/host_shaders/block_linear_unswizzle_3d_comp.h"
+#include "video_core/host_shaders/opengl_convert_s8d24_comp.h"
#include "video_core/host_shaders/opengl_copy_bc4_comp.h"
#include "video_core/host_shaders/pitch_unswizzle_comp.h"
#include "video_core/renderer_opengl/gl_shader_manager.h"
@@ -50,7 +51,8 @@ UtilShaders::UtilShaders(ProgramManager& program_manager_)
block_linear_unswizzle_2d_program(MakeProgram(BLOCK_LINEAR_UNSWIZZLE_2D_COMP)),
block_linear_unswizzle_3d_program(MakeProgram(BLOCK_LINEAR_UNSWIZZLE_3D_COMP)),
pitch_unswizzle_program(MakeProgram(PITCH_UNSWIZZLE_COMP)),
- copy_bc4_program(MakeProgram(OPENGL_COPY_BC4_COMP)) {
+ copy_bc4_program(MakeProgram(OPENGL_COPY_BC4_COMP)),
+ convert_s8d24_program(MakeProgram(OPENGL_CONVERT_S8D24_COMP)) {
const auto swizzle_table = Tegra::Texture::MakeSwizzleTable();
swizzle_table_buffer.Create();
glNamedBufferStorage(swizzle_table_buffer.handle, sizeof(swizzle_table), &swizzle_table, 0);
@@ -248,6 +250,26 @@ void UtilShaders::CopyBC4(Image& dst_image, Image& src_image, std::span<const Im
program_manager.RestoreGuestCompute();
}
+void UtilShaders::ConvertS8D24(Image& dst_image, std::span<const ImageCopy> copies) {
+ static constexpr GLuint BINDING_DESTINATION = 0;
+ static constexpr GLuint LOC_SIZE = 0;
+
+ program_manager.BindComputeProgram(convert_s8d24_program.handle);
+ for (const ImageCopy& copy : copies) {
+ ASSERT(copy.src_subresource.base_layer == 0);
+ ASSERT(copy.src_subresource.num_layers == 1);
+ ASSERT(copy.dst_subresource.base_layer == 0);
+ ASSERT(copy.dst_subresource.num_layers == 1);
+
+ glUniform3ui(LOC_SIZE, copy.extent.width, copy.extent.height, copy.extent.depth);
+ glBindImageTexture(BINDING_DESTINATION, dst_image.StorageHandle(),
+ copy.dst_subresource.base_level, GL_TRUE, 0, GL_READ_WRITE, GL_RGBA8UI);
+ glDispatchCompute(Common::DivCeil(copy.extent.width, 16u),
+ Common::DivCeil(copy.extent.height, 8u), copy.extent.depth);
+ }
+ program_manager.RestoreGuestCompute();
+}
+
GLenum StoreFormat(u32 bytes_per_block) {
switch (bytes_per_block) {
case 1:
diff --git a/src/video_core/renderer_opengl/util_shaders.h b/src/video_core/renderer_opengl/util_shaders.h
index 5de95ea7a..5c132e67f 100644
--- a/src/video_core/renderer_opengl/util_shaders.h
+++ b/src/video_core/renderer_opengl/util_shaders.h
@@ -39,6 +39,8 @@ public:
void CopyBC4(Image& dst_image, Image& src_image,
std::span<const VideoCommon::ImageCopy> copies);
+ void ConvertS8D24(Image& dst_image, std::span<const VideoCommon::ImageCopy> copies);
+
private:
ProgramManager& program_manager;
@@ -49,6 +51,7 @@ private:
OGLProgram block_linear_unswizzle_3d_program;
OGLProgram pitch_unswizzle_program;
OGLProgram copy_bc4_program;
+ OGLProgram convert_s8d24_program;
};
GLenum StoreFormat(u32 bytes_per_block);
diff --git a/src/video_core/renderer_vulkan/blit_image.cpp b/src/video_core/renderer_vulkan/blit_image.cpp
index ec03cca38..abda1c490 100644
--- a/src/video_core/renderer_vulkan/blit_image.cpp
+++ b/src/video_core/renderer_vulkan/blit_image.cpp
@@ -367,17 +367,14 @@ BlitImageHelper::BlitImageHelper(const Device& device_, VKScheduler& scheduler_,
PipelineLayoutCreateInfo(two_textures_set_layout.address()))),
full_screen_vert(BuildShader(device, FULL_SCREEN_TRIANGLE_VERT_SPV)),
blit_color_to_color_frag(BuildShader(device, VULKAN_BLIT_COLOR_FLOAT_FRAG_SPV)),
+ blit_depth_stencil_frag(BuildShader(device, VULKAN_BLIT_DEPTH_STENCIL_FRAG_SPV)),
convert_depth_to_float_frag(BuildShader(device, CONVERT_DEPTH_TO_FLOAT_FRAG_SPV)),
convert_float_to_depth_frag(BuildShader(device, CONVERT_FLOAT_TO_DEPTH_FRAG_SPV)),
convert_abgr8_to_d24s8_frag(BuildShader(device, CONVERT_ABGR8_TO_D24S8_FRAG_SPV)),
convert_d24s8_to_abgr8_frag(BuildShader(device, CONVERT_D24S8_TO_ABGR8_FRAG_SPV)),
convert_s8d24_to_abgr8_frag(BuildShader(device, CONVERT_S8D24_TO_ABGR8_FRAG_SPV)),
linear_sampler(device.GetLogical().CreateSampler(SAMPLER_CREATE_INFO<VK_FILTER_LINEAR>)),
- nearest_sampler(device.GetLogical().CreateSampler(SAMPLER_CREATE_INFO<VK_FILTER_NEAREST>)) {
- if (device.IsExtShaderStencilExportSupported()) {
- blit_depth_stencil_frag = BuildShader(device, VULKAN_BLIT_DEPTH_STENCIL_FRAG_SPV);
- }
-}
+ nearest_sampler(device.GetLogical().CreateSampler(SAMPLER_CREATE_INFO<VK_FILTER_NEAREST>)) {}
BlitImageHelper::~BlitImageHelper() = default;
diff --git a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
index 1c136c410..a2c6d0e6c 100644
--- a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
+++ b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
@@ -127,6 +127,7 @@ struct FormatTuple {
{VK_FORMAT_A2B10G10R10_UNORM_PACK32, Attachable | Storage}, // A2B10G10R10_UNORM
{VK_FORMAT_A2B10G10R10_UINT_PACK32, Attachable | Storage}, // A2B10G10R10_UINT
{VK_FORMAT_A1R5G5B5_UNORM_PACK16, Attachable}, // A1B5G5R5_UNORM (flipped with swizzle)
+ {VK_FORMAT_R5G5B5A1_UNORM_PACK16}, // A5B5G5R1_UNORM (specially swizzled)
{VK_FORMAT_R8_UNORM, Attachable | Storage}, // R8_UNORM
{VK_FORMAT_R8_SNORM, Attachable | Storage}, // R8_SNORM
{VK_FORMAT_R8_SINT, Attachable | Storage}, // R8_SINT
@@ -184,6 +185,7 @@ struct FormatTuple {
{VK_FORMAT_BC3_SRGB_BLOCK}, // BC3_SRGB
{VK_FORMAT_BC7_SRGB_BLOCK}, // BC7_SRGB
{VK_FORMAT_R4G4B4A4_UNORM_PACK16, Attachable}, // A4B4G4R4_UNORM
+ {VK_FORMAT_R4G4_UNORM_PACK8}, // R4G4_UNORM
{VK_FORMAT_ASTC_4x4_SRGB_BLOCK}, // ASTC_2D_4X4_SRGB
{VK_FORMAT_ASTC_8x8_SRGB_BLOCK}, // ASTC_2D_8X8_SRGB
{VK_FORMAT_ASTC_8x5_SRGB_BLOCK}, // ASTC_2D_8X5_SRGB
diff --git a/src/video_core/renderer_vulkan/pipeline_statistics.cpp b/src/video_core/renderer_vulkan/pipeline_statistics.cpp
index bfec931a6..7ccadf084 100644
--- a/src/video_core/renderer_vulkan/pipeline_statistics.cpp
+++ b/src/video_core/renderer_vulkan/pipeline_statistics.cpp
@@ -57,7 +57,7 @@ void PipelineStatistics::Collect(VkPipeline pipeline) {
stage_stats.basic_block_count = GetUint64(statistic);
}
}
- std::lock_guard lock{mutex};
+ std::scoped_lock lock{mutex};
collected_stats.push_back(stage_stats);
}
}
@@ -66,7 +66,7 @@ void PipelineStatistics::Report() const {
double num{};
Stats total;
{
- std::lock_guard lock{mutex};
+ std::scoped_lock lock{mutex};
for (const Stats& stats : collected_stats) {
total.code_size += stats.code_size;
total.register_count += stats.register_count;
diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.cpp b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
index 0ec85682b..b866e9103 100644
--- a/src/video_core/renderer_vulkan/vk_blit_screen.cpp
+++ b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
@@ -94,11 +94,11 @@ std::size_t GetSizeInBytes(const Tegra::FramebufferConfig& framebuffer) {
VkFormat GetFormat(const Tegra::FramebufferConfig& framebuffer) {
switch (framebuffer.pixel_format) {
- case Tegra::FramebufferConfig::PixelFormat::A8B8G8R8_UNORM:
+ case Service::android::PixelFormat::Rgba8888:
return VK_FORMAT_A8B8G8R8_UNORM_PACK32;
- case Tegra::FramebufferConfig::PixelFormat::RGB565_UNORM:
+ case Service::android::PixelFormat::Rgb565:
return VK_FORMAT_R5G6B5_UNORM_PACK16;
- case Tegra::FramebufferConfig::PixelFormat::B8G8R8A8_UNORM:
+ case Service::android::PixelFormat::Bgra8888:
return VK_FORMAT_B8G8R8A8_UNORM;
default:
UNIMPLEMENTED_MSG("Unknown framebuffer pixel format: {}",
@@ -1390,9 +1390,9 @@ void VKBlitScreen::SetVertexData(BufferData& data, const Tegra::FramebufferConfi
auto right = texcoords.right;
switch (framebuffer_transform_flags) {
- case Tegra::FramebufferConfig::TransformFlags::Unset:
+ case Service::android::BufferTransformFlags::Unset:
break;
- case Tegra::FramebufferConfig::TransformFlags::FlipV:
+ case Service::android::BufferTransformFlags::FlipV:
// Flip the framebuffer vertically
left = texcoords.right;
right = texcoords.left;
@@ -1406,8 +1406,9 @@ void VKBlitScreen::SetVertexData(BufferData& data, const Tegra::FramebufferConfi
UNIMPLEMENTED_IF(framebuffer_crop_rect.top != 0);
UNIMPLEMENTED_IF(framebuffer_crop_rect.left != 0);
- f32 scale_u = 1.0f;
- f32 scale_v = 1.0f;
+ f32 scale_u = static_cast<f32>(framebuffer.width) / static_cast<f32>(screen_info.width);
+ f32 scale_v = static_cast<f32>(framebuffer.height) / static_cast<f32>(screen_info.height);
+
// Scale the output by the crop width/height. This is commonly used with 1280x720 rendering
// (e.g. handheld mode) on a 1920x1080 framebuffer.
if (!fsr) {
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
index 5ffd93499..def838c34 100644
--- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
@@ -141,6 +141,18 @@ StagingBufferRef BufferCacheRuntime::DownloadStagingBuffer(size_t size) {
return staging_pool.Request(size, MemoryUsage::Download);
}
+u64 BufferCacheRuntime::GetDeviceLocalMemory() const {
+ return device.GetDeviceLocalMemory();
+}
+
+u64 BufferCacheRuntime::GetDeviceMemoryUsage() const {
+ return device.GetDeviceMemoryUsage();
+}
+
+bool BufferCacheRuntime::CanReportMemoryUsage() const {
+ return device.CanReportMemoryUsage();
+}
+
void BufferCacheRuntime::Finish() {
scheduler.Finish();
}
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.h b/src/video_core/renderer_vulkan/vk_buffer_cache.h
index 1ee0d8420..d7fdd18ff 100644
--- a/src/video_core/renderer_vulkan/vk_buffer_cache.h
+++ b/src/video_core/renderer_vulkan/vk_buffer_cache.h
@@ -65,6 +65,12 @@ public:
void Finish();
+ u64 GetDeviceLocalMemory() const;
+
+ u64 GetDeviceMemoryUsage() const;
+
+ bool CanReportMemoryUsage() const;
+
[[nodiscard]] StagingBufferRef UploadStagingBuffer(size_t size);
[[nodiscard]] StagingBufferRef DownloadStagingBuffer(size_t size);
diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp
index de36bcdb7..97b3594c2 100644
--- a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp
+++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp
@@ -77,7 +77,7 @@ ComputePipeline::ComputePipeline(const Device& device_, DescriptorPool& descript
if (pipeline_statistics) {
pipeline_statistics->Collect(*pipeline);
}
- std::lock_guard lock{build_mutex};
+ std::scoped_lock lock{build_mutex};
is_built = true;
build_condvar.notify_one();
if (shader_notify) {
diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
index d514b71d0..8959d6059 100644
--- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
+++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
@@ -258,7 +258,7 @@ GraphicsPipeline::GraphicsPipeline(
pipeline_statistics->Collect(*pipeline);
}
- std::lock_guard lock{build_mutex};
+ std::scoped_lock lock{build_mutex};
is_built = true;
build_condvar.notify_one();
if (shader_notify) {
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
index 27e59df73..336d1e9dc 100644
--- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
@@ -404,7 +404,7 @@ void PipelineCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading
workers.QueueWork([this, key, env = std::move(env), &state, &callback]() mutable {
ShaderPools pools;
auto pipeline{CreateComputePipeline(pools, key, env, state.statistics.get(), false)};
- std::lock_guard lock{state.mutex};
+ std::scoped_lock lock{state.mutex};
if (pipeline) {
compute_cache.emplace(key, std::move(pipeline));
}
@@ -434,7 +434,7 @@ void PipelineCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading
auto pipeline{CreateGraphicsPipeline(pools, key, MakeSpan(env_ptrs),
state.statistics.get(), false)};
- std::lock_guard lock{state.mutex};
+ std::scoped_lock lock{state.mutex};
graphics_cache.emplace(key, std::move(pipeline));
++state.built;
if (state.has_loaded) {
diff --git a/src/video_core/renderer_vulkan/vk_render_pass_cache.cpp b/src/video_core/renderer_vulkan/vk_render_pass_cache.cpp
index 451ffe019..d22bb6694 100644
--- a/src/video_core/renderer_vulkan/vk_render_pass_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_render_pass_cache.cpp
@@ -36,7 +36,7 @@ VkAttachmentDescription AttachmentDescription(const Device& device, PixelFormat
RenderPassCache::RenderPassCache(const Device& device_) : device{&device_} {}
VkRenderPass RenderPassCache::Get(const RenderPassKey& key) {
- std::lock_guard lock{mutex};
+ std::scoped_lock lock{mutex};
const auto [pair, is_new] = cache.try_emplace(key);
if (!is_new) {
return *pair->second;
diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp
index ad320991b..6a9416457 100644
--- a/src/video_core/renderer_vulkan/vk_scheduler.cpp
+++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp
@@ -73,7 +73,7 @@ void VKScheduler::DispatchWork() {
return;
}
{
- std::lock_guard lock{work_mutex};
+ std::scoped_lock lock{work_mutex};
work_queue.push(std::move(chunk));
}
work_cv.notify_one();
@@ -157,7 +157,7 @@ void VKScheduler::WorkerThread(std::stop_token stop_token) {
if (has_submit) {
AllocateWorkerCommandBuffer();
}
- std::lock_guard reserve_lock{reserve_mutex};
+ std::scoped_lock reserve_lock{reserve_mutex};
chunk_reserve.push_back(std::move(work));
} while (!stop_token.stop_requested());
}
@@ -282,7 +282,7 @@ void VKScheduler::EndRenderPass() {
}
void VKScheduler::AcquireNewChunk() {
- std::lock_guard lock{reserve_mutex};
+ std::scoped_lock lock{reserve_mutex};
if (chunk_reserve.empty()) {
chunk = std::make_unique<CommandChunk>();
return;
diff --git a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp
index 5d5329abf..64a58304b 100644
--- a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp
+++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp
@@ -118,7 +118,7 @@ StagingBufferPool::StagingBufferPool(const Device& device_, MemoryAllocator& mem
.image = nullptr,
.buffer = *stream_buffer,
};
- const auto memory_properties = device.GetPhysical().GetMemoryProperties();
+ const auto memory_properties = device.GetPhysical().GetMemoryProperties().memoryProperties;
VkMemoryAllocateInfo stream_memory_info{
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.pNext = make_dedicated ? &dedicated_info : nullptr,
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
index 83a23b66a..49691ce0c 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
@@ -438,6 +438,32 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
}
}
+[[nodiscard]] SwizzleSource SwapGreenRed(SwizzleSource value) {
+ switch (value) {
+ case SwizzleSource::R:
+ return SwizzleSource::G;
+ case SwizzleSource::G:
+ return SwizzleSource::R;
+ default:
+ return value;
+ }
+}
+
+[[nodiscard]] SwizzleSource SwapSpecial(SwizzleSource value) {
+ switch (value) {
+ case SwizzleSource::A:
+ return SwizzleSource::R;
+ case SwizzleSource::R:
+ return SwizzleSource::A;
+ case SwizzleSource::G:
+ return SwizzleSource::B;
+ case SwizzleSource::B:
+ return SwizzleSource::G;
+ default:
+ return value;
+ }
+}
+
void CopyBufferToImage(vk::CommandBuffer cmdbuf, VkBuffer src_buffer, VkImage image,
VkImageAspectFlags aspect_mask, bool is_initialized,
std::span<const VkBufferImageCopy> copies) {
@@ -554,14 +580,25 @@ void CopyBufferToImage(vk::CommandBuffer cmdbuf, VkBuffer src_buffer, VkImage im
};
}
-[[nodiscard]] bool IsFormatFlipped(PixelFormat format, bool emulate_bgr565) {
+void TryTransformSwizzleIfNeeded(PixelFormat format, std::array<SwizzleSource, 4>& swizzle,
+ bool emulate_bgr565) {
switch (format) {
case PixelFormat::A1B5G5R5_UNORM:
- return true;
+ std::ranges::transform(swizzle, swizzle.begin(), SwapBlueRed);
+ break;
case PixelFormat::B5G6R5_UNORM:
- return emulate_bgr565;
+ if (emulate_bgr565) {
+ std::ranges::transform(swizzle, swizzle.begin(), SwapBlueRed);
+ }
+ break;
+ case PixelFormat::A5B5G5R1_UNORM:
+ std::ranges::transform(swizzle, swizzle.begin(), SwapSpecial);
+ break;
+ case PixelFormat::R4G4_UNORM:
+ std::ranges::transform(swizzle, swizzle.begin(), SwapGreenRed);
+ break;
default:
- return false;
+ break;
}
}
@@ -1189,6 +1226,14 @@ u64 TextureCacheRuntime::GetDeviceLocalMemory() const {
return device.GetDeviceLocalMemory();
}
+u64 TextureCacheRuntime::GetDeviceMemoryUsage() const {
+ return device.GetDeviceMemoryUsage();
+}
+
+bool TextureCacheRuntime::CanReportMemoryUsage() const {
+ return device.CanReportMemoryUsage();
+}
+
void TextureCacheRuntime::TickFrame() {}
Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu_addr_,
@@ -1203,6 +1248,7 @@ Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu
} else {
flags |= VideoCommon::ImageFlagBits::Converted;
}
+ flags |= VideoCommon::ImageFlagBits::CostlyLoad;
}
if (runtime->device.HasDebuggingToolAttached()) {
original_image.SetObjectNameEXT(VideoCommon::Name(*this).c_str());
@@ -1442,8 +1488,7 @@ bool Image::BlitScaleHelper(bool scale_up) {
runtime->blit_image_helper.BlitColor(blit_framebuffer.get(), color_view, dst_region,
src_region, operation, BLIT_OPERATION);
- } else if (!runtime->device.IsBlitDepthStencilSupported() &&
- aspect_mask == (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) {
+ } else if (aspect_mask == (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) {
if (!blit_framebuffer) {
blit_framebuffer = std::make_unique<Framebuffer>(*runtime, nullptr, view_ptr, extent);
}
@@ -1488,9 +1533,7 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI
};
if (!info.IsRenderTarget()) {
swizzle = info.Swizzle();
- if (IsFormatFlipped(format, device->MustEmulateBGR565())) {
- std::ranges::transform(swizzle, swizzle.begin(), SwapBlueRed);
- }
+ TryTransformSwizzleIfNeeded(format, swizzle, device->MustEmulateBGR565());
if ((aspect_mask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) != 0) {
std::ranges::transform(swizzle, swizzle.begin(), ConvertGreenRed);
}
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h
index c81130dd2..cb15b4a1c 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.h
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.h
@@ -55,6 +55,10 @@ public:
u64 GetDeviceLocalMemory() const;
+ u64 GetDeviceMemoryUsage() const;
+
+ bool CanReportMemoryUsage() const;
+
void BlitImage(Framebuffer* dst_framebuffer, ImageView& dst, ImageView& src,
const Region2D& dst_region, const Region2D& src_region,
Tegra::Engines::Fermi2D::Filter filter,
diff --git a/src/video_core/shader_cache.cpp b/src/video_core/shader_cache.cpp
index 87636857d..75031767a 100644
--- a/src/video_core/shader_cache.cpp
+++ b/src/video_core/shader_cache.cpp
@@ -25,7 +25,7 @@ void ShaderCache::InvalidateRegion(VAddr addr, size_t size) {
}
void ShaderCache::OnCPUWrite(VAddr addr, size_t size) {
- std::lock_guard lock{invalidation_mutex};
+ std::scoped_lock lock{invalidation_mutex};
InvalidatePagesInRegion(addr, size);
}
diff --git a/src/video_core/surface.cpp b/src/video_core/surface.cpp
index a36015c8c..5f428d35d 100644
--- a/src/video_core/surface.cpp
+++ b/src/video_core/surface.cpp
@@ -190,13 +190,13 @@ PixelFormat PixelFormatFromRenderTargetFormat(Tegra::RenderTargetFormat format)
}
}
-PixelFormat PixelFormatFromGPUPixelFormat(Tegra::FramebufferConfig::PixelFormat format) {
+PixelFormat PixelFormatFromGPUPixelFormat(Service::android::PixelFormat format) {
switch (format) {
- case Tegra::FramebufferConfig::PixelFormat::A8B8G8R8_UNORM:
+ case Service::android::PixelFormat::Rgba8888:
return PixelFormat::A8B8G8R8_UNORM;
- case Tegra::FramebufferConfig::PixelFormat::RGB565_UNORM:
+ case Service::android::PixelFormat::Rgb565:
return PixelFormat::R5G6B5_UNORM;
- case Tegra::FramebufferConfig::PixelFormat::B8G8R8A8_UNORM:
+ case Service::android::PixelFormat::Bgra8888:
return PixelFormat::B8G8R8A8_UNORM;
default:
UNIMPLEMENTED_MSG("Unimplemented format={}", format);
diff --git a/src/video_core/surface.h b/src/video_core/surface.h
index 33e8d24ab..86fea61ae 100644
--- a/src/video_core/surface.h
+++ b/src/video_core/surface.h
@@ -25,6 +25,7 @@ enum class PixelFormat {
A2B10G10R10_UNORM,
A2B10G10R10_UINT,
A1B5G5R5_UNORM,
+ A5B5G5R1_UNORM,
R8_UNORM,
R8_SNORM,
R8_SINT,
@@ -82,6 +83,7 @@ enum class PixelFormat {
BC3_SRGB,
BC7_SRGB,
A4B4G4R4_UNORM,
+ R4G4_UNORM,
ASTC_2D_4X4_SRGB,
ASTC_2D_8X8_SRGB,
ASTC_2D_8X5_SRGB,
@@ -156,6 +158,7 @@ constexpr std::array<u32, MaxPixelFormat> BLOCK_WIDTH_TABLE = {{
1, // A2B10G10R10_UNORM
1, // A2B10G10R10_UINT
1, // A1B5G5R5_UNORM
+ 1, // A5B5G5R1_UNORM
1, // R8_UNORM
1, // R8_SNORM
1, // R8_SINT
@@ -213,6 +216,7 @@ constexpr std::array<u32, MaxPixelFormat> BLOCK_WIDTH_TABLE = {{
4, // BC3_SRGB
4, // BC7_SRGB
1, // A4B4G4R4_UNORM
+ 1, // R4G4_UNORM
4, // ASTC_2D_4X4_SRGB
8, // ASTC_2D_8X8_SRGB
8, // ASTC_2D_8X5_SRGB
@@ -256,6 +260,7 @@ constexpr std::array<u32, MaxPixelFormat> BLOCK_HEIGHT_TABLE = {{
1, // A2B10G10R10_UNORM
1, // A2B10G10R10_UINT
1, // A1B5G5R5_UNORM
+ 1, // A5B5G5R1_UNORM
1, // R8_UNORM
1, // R8_SNORM
1, // R8_SINT
@@ -313,6 +318,7 @@ constexpr std::array<u32, MaxPixelFormat> BLOCK_HEIGHT_TABLE = {{
4, // BC3_SRGB
4, // BC7_SRGB
1, // A4B4G4R4_UNORM
+ 1, // R4G4_UNORM
4, // ASTC_2D_4X4_SRGB
8, // ASTC_2D_8X8_SRGB
5, // ASTC_2D_8X5_SRGB
@@ -356,6 +362,7 @@ constexpr std::array<u32, MaxPixelFormat> BITS_PER_BLOCK_TABLE = {{
32, // A2B10G10R10_UNORM
32, // A2B10G10R10_UINT
16, // A1B5G5R5_UNORM
+ 16, // A5B5G5R1_UNORM
8, // R8_UNORM
8, // R8_SNORM
8, // R8_SINT
@@ -413,6 +420,7 @@ constexpr std::array<u32, MaxPixelFormat> BITS_PER_BLOCK_TABLE = {{
128, // BC3_SRGB
128, // BC7_UNORM
16, // A4B4G4R4_UNORM
+ 8, // R4G4_UNORM
128, // ASTC_2D_4X4_SRGB
128, // ASTC_2D_8X8_SRGB
128, // ASTC_2D_8X5_SRGB
@@ -460,7 +468,7 @@ PixelFormat PixelFormatFromDepthFormat(Tegra::DepthFormat format);
PixelFormat PixelFormatFromRenderTargetFormat(Tegra::RenderTargetFormat format);
-PixelFormat PixelFormatFromGPUPixelFormat(Tegra::FramebufferConfig::PixelFormat format);
+PixelFormat PixelFormatFromGPUPixelFormat(Service::android::PixelFormat format);
SurfaceType GetFormatType(PixelFormat pixel_format);
diff --git a/src/video_core/texture_cache/format_lookup_table.cpp b/src/video_core/texture_cache/format_lookup_table.cpp
index afa807d5d..20e64a7c2 100644
--- a/src/video_core/texture_cache/format_lookup_table.cpp
+++ b/src/video_core/texture_cache/format_lookup_table.cpp
@@ -63,6 +63,10 @@ PixelFormat PixelFormatFromTextureInfo(TextureFormat format, ComponentType red,
return PixelFormat::A1B5G5R5_UNORM;
case Hash(TextureFormat::A4B4G4R4, UNORM):
return PixelFormat::A4B4G4R4_UNORM;
+ case Hash(TextureFormat::G4R4, UNORM):
+ return PixelFormat::R4G4_UNORM;
+ case Hash(TextureFormat::A5B5G5R1, UNORM):
+ return PixelFormat::A5B5G5R1_UNORM;
case Hash(TextureFormat::R8, UNORM):
return PixelFormat::R8_UNORM;
case Hash(TextureFormat::R8, SNORM):
@@ -143,6 +147,8 @@ PixelFormat PixelFormatFromTextureInfo(TextureFormat format, ComponentType red,
return PixelFormat::S8_UINT_D24_UNORM;
case Hash(TextureFormat::R8G24, UINT, UNORM, UNORM, UNORM, LINEAR):
return PixelFormat::S8_UINT_D24_UNORM;
+ case Hash(TextureFormat::D24S8, UNORM, UINT, UINT, UINT, LINEAR):
+ return PixelFormat::D24_UNORM_S8_UINT;
case Hash(TextureFormat::D32S8, FLOAT, UINT, UNORM, UNORM, LINEAR):
return PixelFormat::D32_FLOAT_S8_UINT;
case Hash(TextureFormat::BC1_RGBA, UNORM, LINEAR):
diff --git a/src/video_core/texture_cache/formatter.h b/src/video_core/texture_cache/formatter.h
index b2c81057b..6f5afc5a9 100644
--- a/src/video_core/texture_cache/formatter.h
+++ b/src/video_core/texture_cache/formatter.h
@@ -38,6 +38,8 @@ struct fmt::formatter<VideoCore::Surface::PixelFormat> : fmt::formatter<fmt::str
return "A2B10G10R10_UINT";
case PixelFormat::A1B5G5R5_UNORM:
return "A1B5G5R5_UNORM";
+ case PixelFormat::A5B5G5R1_UNORM:
+ return "A5B5G5R1_UNORM";
case PixelFormat::R8_UNORM:
return "R8_UNORM";
case PixelFormat::R8_SNORM:
@@ -152,6 +154,8 @@ struct fmt::formatter<VideoCore::Surface::PixelFormat> : fmt::formatter<fmt::str
return "BC7_SRGB";
case PixelFormat::A4B4G4R4_UNORM:
return "A4B4G4R4_UNORM";
+ case PixelFormat::R4G4_UNORM:
+ return "R4G4_UNORM";
case PixelFormat::ASTC_2D_4X4_SRGB:
return "ASTC_2D_4X4_SRGB";
case PixelFormat::ASTC_2D_8X8_SRGB:
diff --git a/src/video_core/texture_cache/image_base.h b/src/video_core/texture_cache/image_base.h
index 89c111c00..dd0106432 100644
--- a/src/video_core/texture_cache/image_base.h
+++ b/src/video_core/texture_cache/image_base.h
@@ -33,11 +33,12 @@ enum class ImageFlagBits : u32 {
///< garbage collection priority
Alias = 1 << 11, ///< This image has aliases and has priority on garbage
///< collection
+ CostlyLoad = 1 << 12, ///< Protected from low-tier GC as it is costly to load back.
// Rescaler
- Rescaled = 1 << 12,
- CheckingRescalable = 1 << 13,
- IsRescalable = 1 << 14,
+ Rescaled = 1 << 13,
+ CheckingRescalable = 1 << 14,
+ IsRescalable = 1 << 15,
};
DECLARE_ENUM_FLAG_OPERATORS(ImageFlagBits)
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h
index 72eeb8bbd..8fef74117 100644
--- a/src/video_core/texture_cache/texture_cache.h
+++ b/src/video_core/texture_cache/texture_cache.h
@@ -50,14 +50,20 @@ TextureCache<P>::TextureCache(Runtime& runtime_, VideoCore::RasterizerInterface&
void(slot_samplers.insert(runtime, sampler_descriptor));
if constexpr (HAS_DEVICE_MEMORY_INFO) {
- const auto device_memory = runtime.GetDeviceLocalMemory();
- const u64 possible_expected_memory = (device_memory * 4) / 10;
- const u64 possible_critical_memory = (device_memory * 7) / 10;
- expected_memory = std::max(possible_expected_memory, DEFAULT_EXPECTED_MEMORY - 256_MiB);
- critical_memory = std::max(possible_critical_memory, DEFAULT_CRITICAL_MEMORY - 512_MiB);
- minimum_memory = 0;
+ const s64 device_memory = static_cast<s64>(runtime.GetDeviceLocalMemory());
+ const s64 min_spacing_expected = device_memory - 1_GiB - 512_MiB;
+ const s64 min_spacing_critical = device_memory - 1_GiB;
+ const s64 mem_threshold = std::min(device_memory, TARGET_THRESHOLD);
+ const s64 min_vacancy_expected = (6 * mem_threshold) / 10;
+ const s64 min_vacancy_critical = (3 * mem_threshold) / 10;
+ expected_memory = static_cast<u64>(
+ std::max(std::min(device_memory - min_vacancy_expected, min_spacing_expected),
+ DEFAULT_EXPECTED_MEMORY));
+ critical_memory = static_cast<u64>(
+ std::max(std::min(device_memory - min_vacancy_critical, min_spacing_critical),
+ DEFAULT_CRITICAL_MEMORY));
+ minimum_memory = static_cast<u64>((device_memory - mem_threshold) / 2);
} else {
- // On OpenGL we can be more conservatives as the driver takes care.
expected_memory = DEFAULT_EXPECTED_MEMORY + 512_MiB;
critical_memory = DEFAULT_CRITICAL_MEMORY + 1_GiB;
minimum_memory = 0;
@@ -66,18 +72,21 @@ TextureCache<P>::TextureCache(Runtime& runtime_, VideoCore::RasterizerInterface&
template <class P>
void TextureCache<P>::RunGarbageCollector() {
- const bool high_priority_mode = total_used_memory >= expected_memory;
- const bool aggressive_mode = total_used_memory >= critical_memory;
- const u64 ticks_to_destroy = aggressive_mode ? 10ULL : high_priority_mode ? 25ULL : 100ULL;
- size_t num_iterations = aggressive_mode ? 300 : (high_priority_mode ? 50 : 10);
- const auto clean_up = [this, &num_iterations, high_priority_mode](ImageId image_id) {
+ bool high_priority_mode = total_used_memory >= expected_memory;
+ bool aggressive_mode = total_used_memory >= critical_memory;
+ const u64 ticks_to_destroy = aggressive_mode ? 10ULL : high_priority_mode ? 25ULL : 50ULL;
+ size_t num_iterations = aggressive_mode ? 40 : (high_priority_mode ? 20 : 10);
+ const auto clean_up = [this, &num_iterations, &high_priority_mode,
+ &aggressive_mode](ImageId image_id) {
if (num_iterations == 0) {
return true;
}
--num_iterations;
auto& image = slot_images[image_id];
- const bool must_download = image.IsSafeDownload();
- if (!high_priority_mode && must_download) {
+ const bool must_download =
+ image.IsSafeDownload() && False(image.flags & ImageFlagBits::BadOverlap);
+ if (!high_priority_mode &&
+ (must_download || True(image.flags & ImageFlagBits::CostlyLoad))) {
return false;
}
if (must_download) {
@@ -92,6 +101,18 @@ void TextureCache<P>::RunGarbageCollector() {
}
UnregisterImage(image_id);
DeleteImage(image_id, image.scale_tick > frame_tick + 5);
+ if (total_used_memory < critical_memory) {
+ if (aggressive_mode) {
+ // Sink the aggresiveness.
+ num_iterations >>= 2;
+ aggressive_mode = false;
+ return false;
+ }
+ if (high_priority_mode && total_used_memory < expected_memory) {
+ num_iterations >>= 1;
+ high_priority_mode = false;
+ }
+ }
return false;
};
lru_cache.ForEachItemBelow(frame_tick - ticks_to_destroy, clean_up);
@@ -99,6 +120,10 @@ void TextureCache<P>::RunGarbageCollector() {
template <class P>
void TextureCache<P>::TickFrame() {
+ // If we can obtain the memory info, use it instead of the estimate.
+ if (runtime.CanReportMemoryUsage()) {
+ total_used_memory = runtime.GetDeviceMemoryUsage();
+ }
if (total_used_memory > minimum_memory) {
RunGarbageCollector();
}
@@ -106,6 +131,7 @@ void TextureCache<P>::TickFrame() {
sentenced_framebuffers.Tick();
sentenced_image_view.Tick();
runtime.TickFrame();
+ critical_gc = 0;
++frame_tick;
}
@@ -1052,6 +1078,9 @@ ImageId TextureCache<P>::JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, VA
for (const ImageId overlap_id : overlap_ids) {
Image& overlap = slot_images[overlap_id];
+ if (True(overlap.flags & ImageFlagBits::GpuModified)) {
+ new_image.flags |= ImageFlagBits::GpuModified;
+ }
if (overlap.info.num_samples != new_image.info.num_samples) {
LOG_WARNING(HW_GPU, "Copying between images with different samples is not implemented");
} else {
@@ -1414,6 +1443,10 @@ void TextureCache<P>::RegisterImage(ImageId image_id) {
tentative_size = EstimatedDecompressedSize(tentative_size, image.info.format);
}
total_used_memory += Common::AlignUp(tentative_size, 1024);
+ if (total_used_memory > critical_memory && critical_gc < GC_EMERGENCY_COUNTS) {
+ RunGarbageCollector();
+ critical_gc++;
+ }
image.lru_index = lru_cache.Insert(image_id, frame_tick);
ForEachGPUPage(image.gpu_addr, image.guest_size_bytes,
@@ -1704,6 +1737,9 @@ void TextureCache<P>::SynchronizeAliases(ImageId image_id) {
most_recent_tick = std::max(most_recent_tick, aliased_image.modification_tick);
aliased_images.push_back(&aliased);
any_rescaled |= True(aliased_image.flags & ImageFlagBits::Rescaled);
+ if (True(aliased_image.flags & ImageFlagBits::GpuModified)) {
+ image.flags |= ImageFlagBits::GpuModified;
+ }
}
}
if (aliased_images.empty()) {
diff --git a/src/video_core/texture_cache/texture_cache_base.h b/src/video_core/texture_cache/texture_cache_base.h
index 647ca0730..b1324edf3 100644
--- a/src/video_core/texture_cache/texture_cache_base.h
+++ b/src/video_core/texture_cache/texture_cache_base.h
@@ -59,8 +59,10 @@ class TextureCache {
/// True when the API can provide info about the memory of the device.
static constexpr bool HAS_DEVICE_MEMORY_INFO = P::HAS_DEVICE_MEMORY_INFO;
- static constexpr u64 DEFAULT_EXPECTED_MEMORY = 1_GiB;
- static constexpr u64 DEFAULT_CRITICAL_MEMORY = 2_GiB;
+ static constexpr s64 TARGET_THRESHOLD = 4_GiB;
+ static constexpr s64 DEFAULT_EXPECTED_MEMORY = 1_GiB + 125_MiB;
+ static constexpr s64 DEFAULT_CRITICAL_MEMORY = 1_GiB + 625_MiB;
+ static constexpr size_t GC_EMERGENCY_COUNTS = 2;
using Runtime = typename P::Runtime;
using Image = typename P::Image;
@@ -372,6 +374,7 @@ private:
u64 minimum_memory;
u64 expected_memory;
u64 critical_memory;
+ size_t critical_gc;
SlotVector<Image> slot_images;
SlotVector<ImageMapView> slot_map_views;
diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp
index 32c10d675..bd05a1f84 100644
--- a/src/video_core/vulkan_common/vulkan_device.cpp
+++ b/src/video_core/vulkan_common/vulkan_device.cpp
@@ -12,12 +12,14 @@
#include <vector>
#include "common/assert.h"
+#include "common/literals.h"
#include "common/settings.h"
#include "video_core/vulkan_common/nsight_aftermath_tracker.h"
#include "video_core/vulkan_common/vulkan_device.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
namespace Vulkan {
+using namespace Common::Literals;
namespace {
namespace Alternatives {
constexpr std::array STENCIL8_UINT{
@@ -43,6 +45,12 @@ constexpr std::array B5G6R5_UNORM_PACK16{
VK_FORMAT_R5G6B5_UNORM_PACK16,
VK_FORMAT_UNDEFINED,
};
+
+constexpr std::array R4G4_UNORM_PACK8{
+ VK_FORMAT_R8_UNORM,
+ VK_FORMAT_UNDEFINED,
+};
+
} // namespace Alternatives
enum class NvidiaArchitecture {
@@ -93,6 +101,8 @@ constexpr const VkFormat* GetFormatAlternatives(VkFormat format) {
return Alternatives::DEPTH16_UNORM_STENCIL8_UINT.data();
case VK_FORMAT_B5G6R5_UNORM_PACK16:
return Alternatives::B5G6R5_UNORM_PACK16.data();
+ case VK_FORMAT_R4G4_UNORM_PACK8:
+ return Alternatives::R4G4_UNORM_PACK8.data();
default:
return nullptr;
}
@@ -120,6 +130,8 @@ std::unordered_map<VkFormat, VkFormatProperties> GetFormatProperties(vk::Physica
VK_FORMAT_A8B8G8R8_SRGB_PACK32,
VK_FORMAT_R5G6B5_UNORM_PACK16,
VK_FORMAT_B5G6R5_UNORM_PACK16,
+ VK_FORMAT_R5G5B5A1_UNORM_PACK16,
+ VK_FORMAT_B5G5R5A1_UNORM_PACK16,
VK_FORMAT_A2B10G10R10_UNORM_PACK32,
VK_FORMAT_A2B10G10R10_UINT_PACK32,
VK_FORMAT_A1R5G5B5_UNORM_PACK16,
@@ -158,7 +170,9 @@ std::unordered_map<VkFormat, VkFormatProperties> GetFormatProperties(vk::Physica
VK_FORMAT_R16G16B16A16_SFLOAT,
VK_FORMAT_B8G8R8A8_UNORM,
VK_FORMAT_B8G8R8A8_SRGB,
+ VK_FORMAT_R4G4_UNORM_PACK8,
VK_FORMAT_R4G4B4A4_UNORM_PACK16,
+ VK_FORMAT_B4G4R4A4_UNORM_PACK16,
VK_FORMAT_D32_SFLOAT,
VK_FORMAT_D16_UNORM,
VK_FORMAT_S8_UINT,
@@ -596,6 +610,11 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
}
logical = vk::Device::Create(physical, queue_cis, extensions, first_next, dld);
+ is_integrated = properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU;
+ is_virtual = properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU;
+ is_non_gpu = properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_OTHER ||
+ properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_CPU;
+
CollectPhysicalMemoryInfo();
CollectTelemetryParameters();
CollectToolingInfo();
@@ -614,6 +633,11 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
khr_push_descriptor = false;
break;
}
+ const u32 nv_major_version = (properties.driverVersion >> 22) & 0x3ff;
+ if (nv_major_version >= 510) {
+ LOG_WARNING(Render_Vulkan, "NVIDIA Drivers >= 510 do not support MSAA image blits");
+ cant_blit_msaa = true;
+ }
}
const bool is_radv = driver_id == VK_DRIVER_ID_MESA_RADV;
if (ext_extended_dynamic_state && is_radv) {
@@ -724,7 +748,7 @@ VkFormat Device::GetSupportedFormat(VkFormat wanted_format, VkFormatFeatureFlags
}
void Device::ReportLoss() const {
- LOG_CRITICAL(Render_Vulkan, "Device loss occured!");
+ LOG_CRITICAL(Render_Vulkan, "Device loss occurred!");
// Wait for the log to flush and for Nsight Aftermath to dump the results
std::this_thread::sleep_for(std::chrono::seconds{15});
@@ -985,6 +1009,7 @@ std::vector<const char*> Device::LoadExtensions(bool requires_surface) {
test(has_khr_swapchain_mutable_format, VK_KHR_SWAPCHAIN_MUTABLE_FORMAT_EXTENSION_NAME,
false);
test(has_ext_line_rasterization, VK_EXT_LINE_RASTERIZATION_EXTENSION_NAME, false);
+ test(ext_memory_budget, VK_EXT_MEMORY_BUDGET_EXTENSION_NAME, true);
if (Settings::values.enable_nsight_aftermath) {
test(nv_device_diagnostics_config, VK_NV_DEVICE_DIAGNOSTICS_CONFIG_EXTENSION_NAME,
true);
@@ -997,7 +1022,7 @@ std::vector<const char*> Device::LoadExtensions(bool requires_surface) {
VkPhysicalDeviceFeatures2KHR features{};
features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR;
- VkPhysicalDeviceProperties2KHR physical_properties;
+ VkPhysicalDeviceProperties2KHR physical_properties{};
physical_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR;
if (has_khr_shader_float16_int8) {
@@ -1267,15 +1292,50 @@ void Device::CollectTelemetryParameters() {
vendor_name = driver.driverName;
}
+u64 Device::GetDeviceMemoryUsage() const {
+ VkPhysicalDeviceMemoryBudgetPropertiesEXT budget;
+ budget.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_BUDGET_PROPERTIES_EXT;
+ budget.pNext = nullptr;
+ physical.GetMemoryProperties(&budget);
+ u64 result{};
+ for (const size_t heap : valid_heap_memory) {
+ result += budget.heapUsage[heap];
+ }
+ return result;
+}
+
void Device::CollectPhysicalMemoryInfo() {
- const auto mem_properties = physical.GetMemoryProperties();
+ VkPhysicalDeviceMemoryBudgetPropertiesEXT budget{};
+ budget.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_BUDGET_PROPERTIES_EXT;
+ const auto mem_info = physical.GetMemoryProperties(ext_memory_budget ? &budget : nullptr);
+ const auto& mem_properties = mem_info.memoryProperties;
const size_t num_properties = mem_properties.memoryHeapCount;
device_access_memory = 0;
+ u64 device_initial_usage = 0;
+ u64 local_memory = 0;
for (size_t element = 0; element < num_properties; ++element) {
- if ((mem_properties.memoryHeaps[element].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) != 0) {
- device_access_memory += mem_properties.memoryHeaps[element].size;
+ const bool is_heap_local =
+ (mem_properties.memoryHeaps[element].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) != 0;
+ if (!is_integrated && !is_heap_local) {
+ continue;
}
+ valid_heap_memory.push_back(element);
+ if (is_heap_local) {
+ local_memory += mem_properties.memoryHeaps[element].size;
+ }
+ if (ext_memory_budget) {
+ device_initial_usage += budget.heapUsage[element];
+ device_access_memory += budget.heapBudget[element];
+ continue;
+ }
+ device_access_memory += mem_properties.memoryHeaps[element].size;
+ }
+ if (!is_integrated) {
+ return;
}
+ const s64 available_memory = static_cast<s64>(device_access_memory - device_initial_usage);
+ device_access_memory = static_cast<u64>(std::max<s64>(
+ std::min<s64>(available_memory - 8_GiB, 4_GiB), static_cast<s64>(local_memory)));
}
void Device::CollectToolingInfo() {
diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h
index 1c7c18bcf..2d709d069 100644
--- a/src/video_core/vulkan_common/vulkan_device.h
+++ b/src/video_core/vulkan_common/vulkan_device.h
@@ -341,6 +341,12 @@ public:
return device_access_memory;
}
+ bool CanReportMemoryUsage() const {
+ return ext_memory_budget;
+ }
+
+ u64 GetDeviceMemoryUsage() const;
+
u32 GetSetsPerPool() const {
return sets_per_pool;
}
@@ -421,6 +427,9 @@ private:
bool is_topology_list_restart_supported{}; ///< Support for primitive restart with list
///< topologies.
bool is_patch_list_restart_supported{}; ///< Support for primitive restart with list patch.
+ bool is_integrated{}; ///< Is GPU an iGPU.
+ bool is_virtual{}; ///< Is GPU a virtual GPU.
+ bool is_non_gpu{}; ///< Is SoftwareRasterizer, FPGA, non-GPU device.
bool nv_viewport_swizzle{}; ///< Support for VK_NV_viewport_swizzle.
bool nv_viewport_array2{}; ///< Support for VK_NV_viewport_array2.
bool nv_geometry_shader_passthrough{}; ///< Support for VK_NV_geometry_shader_passthrough.
@@ -445,6 +454,7 @@ private:
bool ext_shader_atomic_int64{}; ///< Support for VK_KHR_shader_atomic_int64.
bool ext_conservative_rasterization{}; ///< Support for VK_EXT_conservative_rasterization.
bool ext_provoking_vertex{}; ///< Support for VK_EXT_provoking_vertex.
+ bool ext_memory_budget{}; ///< Support for VK_EXT_memory_budget.
bool nv_device_diagnostics_config{}; ///< Support for VK_NV_device_diagnostics_config.
bool has_broken_cube_compatibility{}; ///< Has broken cube compatiblity bit
bool has_renderdoc{}; ///< Has RenderDoc attached
@@ -456,6 +466,7 @@ private:
// Telemetry parameters
std::string vendor_name; ///< Device's driver name.
std::vector<std::string> supported_extensions; ///< Reported Vulkan extensions.
+ std::vector<size_t> valid_heap_memory; ///< Heaps used.
/// Format properties dictionary.
std::unordered_map<VkFormat, VkFormatProperties> format_properties;
diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
index 300a61205..e6e97b332 100644
--- a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
+++ b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
@@ -227,7 +227,7 @@ void MemoryCommit::Release() {
}
MemoryAllocator::MemoryAllocator(const Device& device_, bool export_allocations_)
- : device{device_}, properties{device_.GetPhysical().GetMemoryProperties()},
+ : device{device_}, properties{device_.GetPhysical().GetMemoryProperties().memoryProperties},
export_allocations{export_allocations_},
buffer_image_granularity{
device_.GetPhysical().GetProperties().limits.bufferImageGranularity} {}
diff --git a/src/video_core/vulkan_common/vulkan_wrapper.cpp b/src/video_core/vulkan_common/vulkan_wrapper.cpp
index a794f16dd..742cc39da 100644
--- a/src/video_core/vulkan_common/vulkan_wrapper.cpp
+++ b/src/video_core/vulkan_common/vulkan_wrapper.cpp
@@ -237,8 +237,8 @@ bool Load(VkInstance instance, InstanceDispatch& dld) noexcept {
return X(vkCreateDevice) && X(vkDestroyDevice) && X(vkDestroyDevice) &&
X(vkEnumerateDeviceExtensionProperties) && X(vkEnumeratePhysicalDevices) &&
X(vkGetDeviceProcAddr) && X(vkGetPhysicalDeviceFormatProperties) &&
- X(vkGetPhysicalDeviceMemoryProperties) && X(vkGetPhysicalDeviceProperties) &&
- X(vkGetPhysicalDeviceQueueFamilyProperties);
+ X(vkGetPhysicalDeviceMemoryProperties) && X(vkGetPhysicalDeviceMemoryProperties2) &&
+ X(vkGetPhysicalDeviceProperties) && X(vkGetPhysicalDeviceQueueFamilyProperties);
#undef X
}
@@ -926,9 +926,12 @@ std::vector<VkPresentModeKHR> PhysicalDevice::GetSurfacePresentModesKHR(
return modes;
}
-VkPhysicalDeviceMemoryProperties PhysicalDevice::GetMemoryProperties() const noexcept {
- VkPhysicalDeviceMemoryProperties properties;
- dld->vkGetPhysicalDeviceMemoryProperties(physical_device, &properties);
+VkPhysicalDeviceMemoryProperties2 PhysicalDevice::GetMemoryProperties(
+ void* next_structures) const noexcept {
+ VkPhysicalDeviceMemoryProperties2 properties{};
+ properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PROPERTIES_2;
+ properties.pNext = next_structures;
+ dld->vkGetPhysicalDeviceMemoryProperties2(physical_device, &properties);
return properties;
}
diff --git a/src/video_core/vulkan_common/vulkan_wrapper.h b/src/video_core/vulkan_common/vulkan_wrapper.h
index 53bac627f..0a5f9931c 100644
--- a/src/video_core/vulkan_common/vulkan_wrapper.h
+++ b/src/video_core/vulkan_common/vulkan_wrapper.h
@@ -172,6 +172,7 @@ struct InstanceDispatch {
PFN_vkGetPhysicalDeviceFeatures2KHR vkGetPhysicalDeviceFeatures2KHR{};
PFN_vkGetPhysicalDeviceFormatProperties vkGetPhysicalDeviceFormatProperties{};
PFN_vkGetPhysicalDeviceMemoryProperties vkGetPhysicalDeviceMemoryProperties{};
+ PFN_vkGetPhysicalDeviceMemoryProperties2 vkGetPhysicalDeviceMemoryProperties2{};
PFN_vkGetPhysicalDeviceProperties vkGetPhysicalDeviceProperties{};
PFN_vkGetPhysicalDeviceProperties2KHR vkGetPhysicalDeviceProperties2KHR{};
PFN_vkGetPhysicalDeviceQueueFamilyProperties vkGetPhysicalDeviceQueueFamilyProperties{};
@@ -950,7 +951,8 @@ public:
std::vector<VkPresentModeKHR> GetSurfacePresentModesKHR(VkSurfaceKHR) const;
- VkPhysicalDeviceMemoryProperties GetMemoryProperties() const noexcept;
+ VkPhysicalDeviceMemoryProperties2 GetMemoryProperties(
+ void* next_structures = nullptr) const noexcept;
private:
VkPhysicalDevice physical_device = nullptr;
diff --git a/src/web_service/web_backend.cpp b/src/web_service/web_backend.cpp
index 4208bd044..58b0c2f10 100644
--- a/src/web_service/web_backend.cpp
+++ b/src/web_service/web_backend.cpp
@@ -32,7 +32,7 @@ constexpr std::size_t TIMEOUT_SECONDS = 30;
struct Client::Impl {
Impl(std::string host, std::string username, std::string token)
: host{std::move(host)}, username{std::move(username)}, token{std::move(token)} {
- std::lock_guard lock{jwt_cache.mutex};
+ std::scoped_lock lock{jwt_cache.mutex};
if (this->username == jwt_cache.username && this->token == jwt_cache.token) {
jwt = jwt_cache.jwt;
}
@@ -147,7 +147,7 @@ struct Client::Impl {
if (result.result_code != WebResult::Code::Success) {
LOG_ERROR(WebService, "UpdateJWT failed");
} else {
- std::lock_guard lock{jwt_cache.mutex};
+ std::scoped_lock lock{jwt_cache.mutex};
jwt_cache.username = username;
jwt_cache.token = token;
jwt_cache.jwt = jwt = result.returned_data;
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 30902101d..2ee21f751 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -99,6 +99,9 @@ add_executable(yuzu
configuration/configure_profile_manager.cpp
configuration/configure_profile_manager.h
configuration/configure_profile_manager.ui
+ configuration/configure_ringcon.cpp
+ configuration/configure_ringcon.h
+ configuration/configure_ringcon.ui
configuration/configure_network.cpp
configuration/configure_network.h
configuration/configure_network.ui
@@ -293,7 +296,7 @@ if (YUZU_USE_QT_WEB_ENGINE)
endif ()
if(UNIX AND NOT APPLE)
- install(TARGETS yuzu RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin")
+ install(TARGETS yuzu)
endif()
if (YUZU_USE_BUNDLED_QT)
diff --git a/src/yuzu/aboutdialog.ui b/src/yuzu/aboutdialog.ui
index 27d81cd13..2f7ddc7f3 100644
--- a/src/yuzu/aboutdialog.ui
+++ b/src/yuzu/aboutdialog.ui
@@ -87,7 +87,7 @@
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
-&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:12pt;&quot;&gt;yuzu is an experimental open-source emulator for the Nintendo Switch licensed under GPLv2.0.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:12pt;&quot;&gt;yuzu is an experimental open-source emulator for the Nintendo Switch licensed under GPLv3.0+.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:12pt;&quot;&gt;This software should not be used to play games you have not legally obtained.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
diff --git a/src/yuzu/applets/qt_web_browser.cpp b/src/yuzu/applets/qt_web_browser.cpp
index 398d8f0b6..116ef54db 100644
--- a/src/yuzu/applets/qt_web_browser.cpp
+++ b/src/yuzu/applets/qt_web_browser.cpp
@@ -11,6 +11,9 @@
#include <QWebEngineScriptCollection>
#include <QWebEngineSettings>
#include <QWebEngineUrlScheme>
+
+#include "core/hid/input_interpreter.h"
+#include "yuzu/applets/qt_web_browser_scripts.h"
#endif
#include "common/fs/path_util.h"
diff --git a/src/yuzu/applets/qt_web_browser.h b/src/yuzu/applets/qt_web_browser.h
index 79a08f509..a47059412 100644
--- a/src/yuzu/applets/qt_web_browser.h
+++ b/src/yuzu/applets/qt_web_browser.h
@@ -5,6 +5,7 @@
#pragma once
#include <atomic>
+#include <thread>
#include <QObject>
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp
index 27b0300d2..a1b819ae0 100644
--- a/src/yuzu/bootmanager.cpp
+++ b/src/yuzu/bootmanager.cpp
@@ -933,6 +933,12 @@ void GRenderWindow::CaptureScreenshot(const QString& screenshot_path) {
auto& renderer = system.Renderer();
const f32 res_scale = Settings::values.resolution_info.up_factor;
+ if (renderer.IsScreenshotPending()) {
+ LOG_WARNING(Render,
+ "A screenshot is already requested or in progress, ignoring the request");
+ return;
+ }
+
const Layout::FramebufferLayout layout{Layout::FrameLayoutFromResolutionScale(res_scale)};
screenshot_image = QImage(QSize(layout.width, layout.height), QImage::Format_RGB32);
renderer.RequestScreenshot(
diff --git a/src/yuzu/compatdb.ui b/src/yuzu/compatdb.ui
index fed402176..3ca55eda6 100644
--- a/src/yuzu/compatdb.ui
+++ b/src/yuzu/compatdb.ui
@@ -86,7 +86,7 @@
<item row="4" column="0">
<widget class="QRadioButton" name="radioButton_Great">
<property name="text">
- <string>Great </string>
+ <string>Great</string>
</property>
</widget>
</item>
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 4b943c6ba..ac26b885b 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -60,6 +60,11 @@ const std::array<int, 2> Config::default_stick_mod = {
0,
};
+const std::array<int, 2> Config::default_ringcon_analogs{{
+ Qt::Key_A,
+ Qt::Key_D,
+}};
+
// This shouldn't have anything except static initializers (no functions). So
// QKeySequence(...).toString() is NOT ALLOWED HERE.
// This must be in alphabetical order according to action name as it must have the same order as
@@ -346,6 +351,23 @@ void Config::ReadTouchscreenValues() {
ReadSetting(QStringLiteral("touchscreen_diameter_y"), 15).toUInt();
}
+void Config::ReadHidbusValues() {
+ Settings::values.enable_ring_controller =
+ ReadSetting(QStringLiteral("enable_ring_controller"), true).toBool();
+
+ const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
+ 0, 0, default_ringcon_analogs[0], default_ringcon_analogs[1], 0, 0.05f);
+ auto& ringcon_analogs = Settings::values.ringcon_analogs;
+
+ ringcon_analogs =
+ qt_config->value(QStringLiteral("ring_controller"), QString::fromStdString(default_param))
+ .toString()
+ .toStdString();
+ if (ringcon_analogs.empty()) {
+ ringcon_analogs = default_param;
+ }
+}
+
void Config::ReadAudioValues() {
qt_config->beginGroup(QStringLiteral("Audio"));
@@ -369,6 +391,7 @@ void Config::ReadControlValues() {
ReadMouseValues();
ReadTouchscreenValues();
ReadMotionTouchValues();
+ ReadHidbusValues();
#ifdef _WIN32
ReadBasicSetting(Settings::values.enable_raw_input);
@@ -775,6 +798,7 @@ void Config::ReadUIValues() {
ReadBasicSetting(UISettings::values.pause_when_in_background);
ReadBasicSetting(UISettings::values.mute_when_in_background);
ReadBasicSetting(UISettings::values.hide_mouse);
+ ReadBasicSetting(UISettings::values.disable_web_applet);
qt_config->endGroup();
}
@@ -961,6 +985,16 @@ void Config::SaveMotionTouchValues() {
qt_config->endArray();
}
+void Config::SaveHidbusValues() {
+ WriteBasicSetting(Settings::values.enable_ring_controller);
+
+ const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
+ 0, 0, default_ringcon_analogs[0], default_ringcon_analogs[1], 0, 0.05f);
+ WriteSetting(QStringLiteral("ring_controller"),
+ QString::fromStdString(Settings::values.ringcon_analogs),
+ QString::fromStdString(default_param));
+}
+
void Config::SaveValues() {
if (global) {
SaveControlValues();
@@ -1001,6 +1035,7 @@ void Config::SaveControlValues() {
SaveMouseValues();
SaveTouchscreenValues();
SaveMotionTouchValues();
+ SaveHidbusValues();
WriteGlobalSetting(Settings::values.use_docked_mode);
WriteGlobalSetting(Settings::values.vibration_enabled);
@@ -1308,6 +1343,7 @@ void Config::SaveUIValues() {
WriteBasicSetting(UISettings::values.pause_when_in_background);
WriteBasicSetting(UISettings::values.mute_when_in_background);
WriteBasicSetting(UISettings::values.hide_mouse);
+ WriteBasicSetting(UISettings::values.disable_web_applet);
qt_config->endGroup();
}
diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h
index ae3e36a11..f0ab6bdaa 100644
--- a/src/yuzu/configuration/config.h
+++ b/src/yuzu/configuration/config.h
@@ -42,6 +42,7 @@ public:
static const std::array<int, Settings::NativeMotion::NumMotions> default_motions;
static const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> default_analogs;
static const std::array<int, 2> default_stick_mod;
+ static const std::array<int, 2> default_ringcon_analogs;
static const std::array<int, Settings::NativeMouseButton::NumMouseButtons>
default_mouse_buttons;
static const std::array<int, Settings::NativeKeyboard::NumKeyboardKeys> default_keyboard_keys;
@@ -66,6 +67,7 @@ private:
void ReadMouseValues();
void ReadTouchscreenValues();
void ReadMotionTouchValues();
+ void ReadHidbusValues();
// Read functions bases off the respective config section names.
void ReadAudioValues();
@@ -93,6 +95,7 @@ private:
void SaveMouseValues();
void SaveTouchscreenValues();
void SaveMotionTouchValues();
+ void SaveHidbusValues();
// Save functions based off the respective config section names.
void SaveAudioValues();
diff --git a/src/yuzu/configuration/configure_cpu.ui b/src/yuzu/configuration/configure_cpu.ui
index 5d80a8c91..8ae569ee6 100644
--- a/src/yuzu/configuration/configure_cpu.ui
+++ b/src/yuzu/configuration/configure_cpu.ui
@@ -52,6 +52,11 @@
<string>Unsafe</string>
</property>
</item>
+ <item>
+ <property name="text">
+ <string>Paranoid (disables most optimizations)</string>
+ </property>
+ </item>
</widget>
</item>
</layout>
diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp
index c1cf4050c..bd50f7a68 100644
--- a/src/yuzu/configuration/configure_debug.cpp
+++ b/src/yuzu/configuration/configure_debug.cpp
@@ -59,6 +59,13 @@ void ConfigureDebug::SetConfiguration() {
ui->disable_loop_safety_checks->setChecked(
Settings::values.disable_shader_loop_safety_checks.GetValue());
ui->extended_logging->setChecked(Settings::values.extended_logging.GetValue());
+
+#ifdef YUZU_USE_QT_WEB_ENGINE
+ ui->disable_web_applet->setChecked(UISettings::values.disable_web_applet.GetValue());
+#else
+ ui->disable_web_applet->setEnabled(false);
+ ui->disable_web_applet->setText(QString::fromUtf8("Web applet not compiled"));
+#endif
}
void ConfigureDebug::ApplyConfiguration() {
@@ -80,6 +87,7 @@ void ConfigureDebug::ApplyConfiguration() {
ui->disable_loop_safety_checks->isChecked();
Settings::values.disable_macro_jit = ui->disable_macro_jit->isChecked();
Settings::values.extended_logging = ui->extended_logging->isChecked();
+ UISettings::values.disable_web_applet = ui->disable_web_applet->isChecked();
Debugger::ToggleConsole();
Common::Log::Filter filter;
filter.ParseFilterString(Settings::values.log_filter.GetValue());
diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui
index 4dd870855..c1d90d588 100644
--- a/src/yuzu/configuration/configure_debug.ui
+++ b/src/yuzu/configuration/configure_debug.ui
@@ -8,49 +8,49 @@
<property name="title">
<string>Logging</string>
</property>
- <layout class="QGridLayout" name="gridLayout_1">
- <item row="0" column="0" colspan="2">
- <layout class="QHBoxLayout" name="horizontalLayout_1">
- <item>
- <widget class="QLabel" name="label_1">
- <property name="text">
- <string>Global Log Filter</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QLineEdit" name="log_filter_edit"/>
- </item>
- </layout>
- </item>
- <item row="1" column="0">
- <widget class="QCheckBox" name="toggle_console">
- <property name="text">
- <string>Show Log in Console</string>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="QPushButton" name="open_log_button">
- <property name="text">
- <string>Open Log Location</string>
- </property>
- </widget>
- </item>
- <item row="2" column="0">
- <widget class="QCheckBox" name="extended_logging">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="toolTip">
- <string>When checked, the max size of the log increases from 100 MB to 1 GB</string>
- </property>
+ <layout class="QGridLayout" name="gridLayout_1">
+ <item row="0" column="0" colspan="2">
+ <layout class="QHBoxLayout" name="horizontalLayout_1">
+ <item>
+ <widget class="QLabel" name="label_1">
<property name="text">
- <string>Enable Extended Logging**</string>
+ <string>Global Log Filter</string>
</property>
- </widget>
- </item>
- </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="log_filter_edit"/>
+ </item>
+ </layout>
+ </item>
+ <item row="1" column="0">
+ <widget class="QCheckBox" name="toggle_console">
+ <property name="text">
+ <string>Show Log in Console</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QPushButton" name="open_log_button">
+ <property name="text">
+ <string>Open Log Location</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QCheckBox" name="extended_logging">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="toolTip">
+ <string>When checked, the max size of the log increases from 100 MB to 1 GB</string>
+ </property>
+ <property name="text">
+ <string>Enable Extended Logging**</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
</widget>
</item>
<item>
@@ -183,7 +183,7 @@
<string>Advanced</string>
</property>
<layout class="QGridLayout" name="gridLayout_4">
- <item> row="0" column="0">
+ <item row="0" column="0">
<widget class="QCheckBox" name="quest_flag">
<property name="text">
<string>Kiosk (Quest) Mode</string>
@@ -214,7 +214,14 @@
<item row="1" column="1">
<widget class="QCheckBox" name="enable_all_controllers">
<property name="text">
- <string>Enable all Controller Types</string>
+ <string>Enable All Controller Types</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QCheckBox" name="disable_web_applet">
+ <property name="text">
+ <string>Disable Web Applet</string>
</property>
</widget>
</item>
diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp
index 69458a3ff..b415a1cc4 100644
--- a/src/yuzu/configuration/configure_dialog.cpp
+++ b/src/yuzu/configuration/configure_dialog.cpp
@@ -25,6 +25,7 @@
#include "yuzu/configuration/configure_ui.h"
#include "yuzu/configuration/configure_web.h"
#include "yuzu/hotkeys.h"
+#include "yuzu/uisettings.h"
ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry,
InputCommon::InputSubsystem* input_subsystem,
@@ -169,6 +170,8 @@ void ConfigureDialog::PopulateSelectionList() {
void ConfigureDialog::OnLanguageChanged(const QString& locale) {
emit LanguageChanged(locale);
+ // Reloading the game list is needed to force retranslation.
+ UISettings::values.is_game_list_reload_pending = true;
// first apply the configuration, and then restore the display
ApplyConfiguration();
RetranslateUI();
diff --git a/src/yuzu/configuration/configure_hotkeys.cpp b/src/yuzu/configuration/configure_hotkeys.cpp
index 53e629a5e..6679e9c53 100644
--- a/src/yuzu/configuration/configure_hotkeys.cpp
+++ b/src/yuzu/configuration/configure_hotkeys.cpp
@@ -35,8 +35,9 @@ ConfigureHotkeys::ConfigureHotkeys(Core::HID::HIDCore& hid_core, QWidget* parent
ui->hotkey_list->setContextMenuPolicy(Qt::CustomContextMenu);
ui->hotkey_list->setModel(model);
- ui->hotkey_list->setColumnWidth(name_column, 200);
- ui->hotkey_list->resizeColumnToContents(hotkey_column);
+ ui->hotkey_list->header()->setStretchLastSection(false);
+ ui->hotkey_list->header()->setSectionResizeMode(name_column, QHeaderView::ResizeMode::Stretch);
+ ui->hotkey_list->header()->setMinimumSectionSize(150);
connect(ui->button_restore_defaults, &QPushButton::clicked, this,
&ConfigureHotkeys::RestoreDefaults);
@@ -76,8 +77,8 @@ void ConfigureHotkeys::Populate(const HotkeyRegistry& registry) {
}
ui->hotkey_list->expandAll();
- ui->hotkey_list->resizeColumnToContents(name_column);
ui->hotkey_list->resizeColumnToContents(hotkey_column);
+ ui->hotkey_list->resizeColumnToContents(controller_column);
}
void ConfigureHotkeys::changeEvent(QEvent* event) {
diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp
index 4ca74a5f7..73d7ba24b 100644
--- a/src/yuzu/configuration/configure_input.cpp
+++ b/src/yuzu/configuration/configure_input.cpp
@@ -20,6 +20,7 @@
#include "yuzu/configuration/configure_input_advanced.h"
#include "yuzu/configuration/configure_input_player.h"
#include "yuzu/configuration/configure_motion_touch.h"
+#include "yuzu/configuration/configure_ringcon.h"
#include "yuzu/configuration/configure_touchscreen_advanced.h"
#include "yuzu/configuration/configure_vibration.h"
#include "yuzu/configuration/input_profiles.h"
@@ -158,6 +159,10 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
[this, input_subsystem] {
CallConfigureDialog<ConfigureMotionTouch>(*this, input_subsystem);
});
+ connect(advanced, &ConfigureInputAdvanced::CallRingControllerDialog,
+ [this, input_subsystem, &hid_core] {
+ CallConfigureDialog<ConfigureRingController>(*this, input_subsystem, hid_core);
+ });
connect(ui->vibrationButton, &QPushButton::clicked,
[this, &hid_core] { CallConfigureDialog<ConfigureVibration>(*this, hid_core); });
diff --git a/src/yuzu/configuration/configure_input_advanced.cpp b/src/yuzu/configuration/configure_input_advanced.cpp
index 20fc2599d..8fd1f4a38 100644
--- a/src/yuzu/configuration/configure_input_advanced.cpp
+++ b/src/yuzu/configuration/configure_input_advanced.cpp
@@ -79,13 +79,17 @@ ConfigureInputAdvanced::ConfigureInputAdvanced(QWidget* parent)
&ConfigureInputAdvanced::UpdateUIEnabled);
connect(ui->touchscreen_enabled, &QCheckBox::stateChanged, this,
&ConfigureInputAdvanced::UpdateUIEnabled);
+ connect(ui->enable_ring_controller, &QCheckBox::stateChanged, this,
+ &ConfigureInputAdvanced::UpdateUIEnabled);
connect(ui->debug_configure, &QPushButton::clicked, this,
[this] { CallDebugControllerDialog(); });
connect(ui->touchscreen_advanced, &QPushButton::clicked, this,
[this] { CallTouchscreenConfigDialog(); });
connect(ui->buttonMotionTouch, &QPushButton::clicked, this,
- &ConfigureInputAdvanced::CallMotionTouchConfigDialog);
+ [this] { CallMotionTouchConfigDialog(); });
+ connect(ui->ring_controller_configure, &QPushButton::clicked, this,
+ [this] { CallRingControllerDialog(); });
#ifndef _WIN32
ui->enable_raw_input->setVisible(false);
@@ -132,6 +136,7 @@ void ConfigureInputAdvanced::ApplyConfiguration() {
Settings::values.enable_raw_input = ui->enable_raw_input->isChecked();
Settings::values.enable_udp_controller = ui->enable_udp_controller->isChecked();
Settings::values.controller_navigation = ui->controller_navigation->isChecked();
+ Settings::values.enable_ring_controller = ui->enable_ring_controller->isChecked();
}
void ConfigureInputAdvanced::LoadConfiguration() {
@@ -164,6 +169,7 @@ void ConfigureInputAdvanced::LoadConfiguration() {
ui->enable_raw_input->setChecked(Settings::values.enable_raw_input.GetValue());
ui->enable_udp_controller->setChecked(Settings::values.enable_udp_controller.GetValue());
ui->controller_navigation->setChecked(Settings::values.controller_navigation.GetValue());
+ ui->enable_ring_controller->setChecked(Settings::values.enable_ring_controller.GetValue());
UpdateUIEnabled();
}
@@ -185,4 +191,5 @@ void ConfigureInputAdvanced::UpdateUIEnabled() {
ui->touchscreen_advanced->setEnabled(ui->touchscreen_enabled->isChecked());
ui->mouse_panning->setEnabled(!ui->mouse_enabled->isChecked());
ui->mouse_panning_sensitivity->setEnabled(!ui->mouse_enabled->isChecked());
+ ui->ring_controller_configure->setEnabled(ui->enable_ring_controller->isChecked());
}
diff --git a/src/yuzu/configuration/configure_input_advanced.h b/src/yuzu/configuration/configure_input_advanced.h
index 3083d55c1..4472cb846 100644
--- a/src/yuzu/configuration/configure_input_advanced.h
+++ b/src/yuzu/configuration/configure_input_advanced.h
@@ -29,6 +29,7 @@ signals:
void CallMouseConfigDialog();
void CallTouchscreenConfigDialog();
void CallMotionTouchConfigDialog();
+ void CallRingControllerDialog();
private:
void changeEvent(QEvent* event) override;
diff --git a/src/yuzu/configuration/configure_input_advanced.ui b/src/yuzu/configuration/configure_input_advanced.ui
index 66f2075f2..14403cb10 100644
--- a/src/yuzu/configuration/configure_input_advanced.ui
+++ b/src/yuzu/configuration/configure_input_advanced.ui
@@ -2603,6 +2603,20 @@
</property>
</widget>
</item>
+ <item row="4" column="0">
+ <widget class="QCheckBox" name="enable_ring_controller">
+ <property name="text">
+ <string>Ring Controller</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="2">
+ <widget class="QPushButton" name="ring_controller_configure">
+ <property name="text">
+ <string>Configure</string>
+ </property>
+ </widget>
+ </item>
</layout>
</widget>
</item>
diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp
index ac0a9d7de..b4f5d172a 100644
--- a/src/yuzu/configuration/configure_input_player.cpp
+++ b/src/yuzu/configuration/configure_input_player.cpp
@@ -10,6 +10,7 @@
#include <QMenu>
#include <QMessageBox>
#include <QTimer>
+#include "common/assert.h"
#include "common/param_package.h"
#include "core/hid/emulated_controller.h"
#include "core/hid/hid_core.h"
@@ -119,6 +120,23 @@ QString GetButtonName(Common::Input::ButtonNames button_name) {
}
}
+QString GetDirectionName(const std::string& direction) {
+ if (direction == "left") {
+ return QObject::tr("Left");
+ }
+ if (direction == "right") {
+ return QObject::tr("Right");
+ }
+ if (direction == "up") {
+ return QObject::tr("Up");
+ }
+ if (direction == "down") {
+ return QObject::tr("Down");
+ }
+ UNIMPLEMENTED_MSG("Unimplemented direction name={}", direction);
+ return QString::fromStdString(direction);
+}
+
void SetAnalogParam(const Common::ParamPackage& input_param, Common::ParamPackage& analog_param,
const std::string& button_name) {
// The poller returned a complete axis, so set all the buttons
@@ -162,7 +180,7 @@ QString ConfigureInputPlayer::ButtonToText(const Common::ParamPackage& param) {
if (common_button_name == Common::Input::ButtonNames::Value) {
if (param.Has("hat")) {
- const QString hat = QString::fromStdString(param.Get("direction", ""));
+ const QString hat = GetDirectionName(param.Get("direction", ""));
return QObject::tr("%1%2Hat %3").arg(toggle, inverted, hat);
}
if (param.Has("axis")) {
@@ -1426,10 +1444,10 @@ void ConfigureInputPlayer::mousePressEvent(QMouseEvent* event) {
}
void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) {
- event->ignore();
if (!input_setter || !event) {
return;
}
+ event->ignore();
if (event->key() != Qt::Key_Escape) {
input_subsystem->GetKeyboard()->PressKey(event->key());
}
diff --git a/src/yuzu/configuration/configure_per_game_addons.cpp b/src/yuzu/configuration/configure_per_game_addons.cpp
index 21e51d749..7893a85bb 100644
--- a/src/yuzu/configuration/configure_per_game_addons.cpp
+++ b/src/yuzu/configuration/configure_per_game_addons.cpp
@@ -47,6 +47,10 @@ ConfigurePerGameAddons::ConfigurePerGameAddons(Core::System& system_, QWidget* p
item_model->setHeaderData(0, Qt::Horizontal, tr("Patch Name"));
item_model->setHeaderData(1, Qt::Horizontal, tr("Version"));
+ tree_view->header()->setStretchLastSection(false);
+ tree_view->header()->setSectionResizeMode(0, QHeaderView::ResizeMode::Stretch);
+ tree_view->header()->setMinimumSectionSize(150);
+
// We must register all custom types with the Qt Automoc system so that we are able to use it
// with signals/slots. In this case, QList falls under the umbrella of custom types.
qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>");
@@ -138,5 +142,5 @@ void ConfigurePerGameAddons::LoadConfiguration() {
item_model->appendRow(list_items.back());
}
- tree_view->setColumnWidth(0, 5 * tree_view->width() / 16);
+ tree_view->resizeColumnToContents(1);
}
diff --git a/src/yuzu/configuration/configure_ringcon.cpp b/src/yuzu/configuration/configure_ringcon.cpp
new file mode 100644
index 000000000..f1cdd4fed
--- /dev/null
+++ b/src/yuzu/configuration/configure_ringcon.cpp
@@ -0,0 +1,424 @@
+// Copyright 2022 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <memory>
+#include <QKeyEvent>
+#include <QMenu>
+#include <QTimer>
+
+#include "core/hid/emulated_devices.h"
+#include "core/hid/hid_core.h"
+#include "input_common/drivers/keyboard.h"
+#include "input_common/drivers/mouse.h"
+#include "input_common/main.h"
+#include "ui_configure_ringcon.h"
+#include "yuzu/bootmanager.h"
+#include "yuzu/configuration/config.h"
+#include "yuzu/configuration/configure_ringcon.h"
+
+const std::array<std::string, ConfigureRingController::ANALOG_SUB_BUTTONS_NUM>
+ ConfigureRingController::analog_sub_buttons{{
+ "left",
+ "right",
+ }};
+
+namespace {
+
+QString GetKeyName(int key_code) {
+ switch (key_code) {
+ case Qt::Key_Shift:
+ return QObject::tr("Shift");
+ case Qt::Key_Control:
+ return QObject::tr("Ctrl");
+ case Qt::Key_Alt:
+ return QObject::tr("Alt");
+ case Qt::Key_Meta:
+ return {};
+ default:
+ return QKeySequence(key_code).toString();
+ }
+}
+
+QString GetButtonName(Common::Input::ButtonNames button_name) {
+ switch (button_name) {
+ case Common::Input::ButtonNames::ButtonLeft:
+ return QObject::tr("Left");
+ case Common::Input::ButtonNames::ButtonRight:
+ return QObject::tr("Right");
+ case Common::Input::ButtonNames::ButtonDown:
+ return QObject::tr("Down");
+ case Common::Input::ButtonNames::ButtonUp:
+ return QObject::tr("Up");
+ case Common::Input::ButtonNames::TriggerZ:
+ return QObject::tr("Z");
+ case Common::Input::ButtonNames::TriggerR:
+ return QObject::tr("R");
+ case Common::Input::ButtonNames::TriggerL:
+ return QObject::tr("L");
+ case Common::Input::ButtonNames::ButtonA:
+ return QObject::tr("A");
+ case Common::Input::ButtonNames::ButtonB:
+ return QObject::tr("B");
+ case Common::Input::ButtonNames::ButtonX:
+ return QObject::tr("X");
+ case Common::Input::ButtonNames::ButtonY:
+ return QObject::tr("Y");
+ case Common::Input::ButtonNames::ButtonStart:
+ return QObject::tr("Start");
+ case Common::Input::ButtonNames::L1:
+ return QObject::tr("L1");
+ case Common::Input::ButtonNames::L2:
+ return QObject::tr("L2");
+ case Common::Input::ButtonNames::L3:
+ return QObject::tr("L3");
+ case Common::Input::ButtonNames::R1:
+ return QObject::tr("R1");
+ case Common::Input::ButtonNames::R2:
+ return QObject::tr("R2");
+ case Common::Input::ButtonNames::R3:
+ return QObject::tr("R3");
+ case Common::Input::ButtonNames::Circle:
+ return QObject::tr("Circle");
+ case Common::Input::ButtonNames::Cross:
+ return QObject::tr("Cross");
+ case Common::Input::ButtonNames::Square:
+ return QObject::tr("Square");
+ case Common::Input::ButtonNames::Triangle:
+ return QObject::tr("Triangle");
+ case Common::Input::ButtonNames::Share:
+ return QObject::tr("Share");
+ case Common::Input::ButtonNames::Options:
+ return QObject::tr("Options");
+ default:
+ return QObject::tr("[undefined]");
+ }
+}
+
+void SetAnalogParam(const Common::ParamPackage& input_param, Common::ParamPackage& analog_param,
+ const std::string& button_name) {
+ // The poller returned a complete axis, so set all the buttons
+ if (input_param.Has("axis_x") && input_param.Has("axis_y")) {
+ analog_param = input_param;
+ return;
+ }
+ // Check if the current configuration has either no engine or an axis binding.
+ // Clears out the old binding and adds one with analog_from_button.
+ if (!analog_param.Has("engine") || analog_param.Has("axis_x") || analog_param.Has("axis_y")) {
+ analog_param = {
+ {"engine", "analog_from_button"},
+ };
+ }
+ analog_param.Set(button_name, input_param.Serialize());
+}
+} // namespace
+
+ConfigureRingController::ConfigureRingController(QWidget* parent,
+ InputCommon::InputSubsystem* input_subsystem_,
+ Core::HID::HIDCore& hid_core_)
+ : QDialog(parent), timeout_timer(std::make_unique<QTimer>()),
+ poll_timer(std::make_unique<QTimer>()), input_subsystem{input_subsystem_},
+
+ ui(std::make_unique<Ui::ConfigureRingController>()) {
+ ui->setupUi(this);
+
+ analog_map_buttons = {
+ ui->buttonRingAnalogPull,
+ ui->buttonRingAnalogPush,
+ };
+
+ emulated_device = hid_core_.GetEmulatedDevices();
+ emulated_device->SaveCurrentConfig();
+ emulated_device->EnableConfiguration();
+
+ LoadConfiguration();
+
+ for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
+ auto* const analog_button = analog_map_buttons[sub_button_id];
+
+ if (analog_button == nullptr) {
+ continue;
+ }
+
+ connect(analog_button, &QPushButton::clicked, [=, this] {
+ HandleClick(
+ analog_map_buttons[sub_button_id],
+ [=, this](const Common::ParamPackage& params) {
+ Common::ParamPackage param = emulated_device->GetRingParam();
+ SetAnalogParam(params, param, analog_sub_buttons[sub_button_id]);
+ emulated_device->SetRingParam(param);
+ },
+ InputCommon::Polling::InputType::Stick);
+ });
+
+ analog_button->setContextMenuPolicy(Qt::CustomContextMenu);
+
+ connect(analog_button, &QPushButton::customContextMenuRequested,
+ [=, this](const QPoint& menu_location) {
+ QMenu context_menu;
+ Common::ParamPackage param = emulated_device->GetRingParam();
+ context_menu.addAction(tr("Clear"), [&] {
+ emulated_device->SetRingParam({});
+ analog_map_buttons[sub_button_id]->setText(tr("[not set]"));
+ });
+ context_menu.addAction(tr("Invert axis"), [&] {
+ const bool invert_value = param.Get("invert_x", "+") == "-";
+ const std::string invert_str = invert_value ? "+" : "-";
+ param.Set("invert_x", invert_str);
+ emulated_device->SetRingParam(param);
+ for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM;
+ ++sub_button_id) {
+ analog_map_buttons[sub_button_id]->setText(
+ AnalogToText(param, analog_sub_buttons[sub_button_id]));
+ }
+ });
+ context_menu.exec(
+ analog_map_buttons[sub_button_id]->mapToGlobal(menu_location));
+ });
+ }
+
+ connect(ui->sliderRingAnalogDeadzone, &QSlider::valueChanged, [=, this] {
+ Common::ParamPackage param = emulated_device->GetRingParam();
+ const auto slider_value = ui->sliderRingAnalogDeadzone->value();
+ ui->labelRingAnalogDeadzone->setText(tr("Deadzone: %1%").arg(slider_value));
+ param.Set("deadzone", slider_value / 100.0f);
+ emulated_device->SetRingParam(param);
+ });
+
+ connect(ui->restore_defaults_button, &QPushButton::clicked, this,
+ &ConfigureRingController::RestoreDefaults);
+
+ timeout_timer->setSingleShot(true);
+ connect(timeout_timer.get(), &QTimer::timeout, [this] { SetPollingResult({}, true); });
+
+ connect(poll_timer.get(), &QTimer::timeout, [this] {
+ const auto& params = input_subsystem->GetNextInput();
+ if (params.Has("engine") && IsInputAcceptable(params)) {
+ SetPollingResult(params, false);
+ return;
+ }
+ });
+
+ resize(0, 0);
+}
+
+ConfigureRingController::~ConfigureRingController() {
+ emulated_device->DisableConfiguration();
+};
+
+void ConfigureRingController::changeEvent(QEvent* event) {
+ if (event->type() == QEvent::LanguageChange) {
+ RetranslateUI();
+ }
+
+ QDialog::changeEvent(event);
+}
+
+void ConfigureRingController::RetranslateUI() {
+ ui->retranslateUi(this);
+}
+
+void ConfigureRingController::UpdateUI() {
+ RetranslateUI();
+ const Common::ParamPackage param = emulated_device->GetRingParam();
+
+ for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
+ auto* const analog_button = analog_map_buttons[sub_button_id];
+
+ if (analog_button == nullptr) {
+ continue;
+ }
+
+ analog_button->setText(AnalogToText(param, analog_sub_buttons[sub_button_id]));
+ }
+
+ const auto deadzone_label = ui->labelRingAnalogDeadzone;
+ const auto deadzone_slider = ui->sliderRingAnalogDeadzone;
+
+ int slider_value = static_cast<int>(param.Get("deadzone", 0.15f) * 100);
+ deadzone_label->setText(tr("Deadzone: %1%").arg(slider_value));
+ deadzone_slider->setValue(slider_value);
+}
+
+void ConfigureRingController::ApplyConfiguration() {
+ emulated_device->DisableConfiguration();
+ emulated_device->SaveCurrentConfig();
+ emulated_device->EnableConfiguration();
+}
+
+void ConfigureRingController::LoadConfiguration() {
+ UpdateUI();
+}
+
+void ConfigureRingController::RestoreDefaults() {
+ const std::string default_ring_string = InputCommon::GenerateAnalogParamFromKeys(
+ 0, 0, Config::default_ringcon_analogs[0], Config::default_ringcon_analogs[1], 0, 0.05f);
+ emulated_device->SetRingParam(Common::ParamPackage(default_ring_string));
+ UpdateUI();
+}
+
+void ConfigureRingController::HandleClick(
+ QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter,
+ InputCommon::Polling::InputType type) {
+ button->setText(tr("[waiting]"));
+ button->setFocus();
+
+ input_setter = new_input_setter;
+
+ input_subsystem->BeginMapping(type);
+
+ QWidget::grabMouse();
+ QWidget::grabKeyboard();
+
+ timeout_timer->start(2500); // Cancel after 2.5 seconds
+ poll_timer->start(25); // Check for new inputs every 25ms
+}
+
+void ConfigureRingController::SetPollingResult(const Common::ParamPackage& params, bool abort) {
+ timeout_timer->stop();
+ poll_timer->stop();
+ input_subsystem->StopMapping();
+
+ QWidget::releaseMouse();
+ QWidget::releaseKeyboard();
+
+ if (!abort) {
+ (*input_setter)(params);
+ }
+
+ UpdateUI();
+
+ input_setter = std::nullopt;
+}
+
+bool ConfigureRingController::IsInputAcceptable(const Common::ParamPackage& params) const {
+ return true;
+}
+
+void ConfigureRingController::mousePressEvent(QMouseEvent* event) {
+ if (!input_setter || !event) {
+ return;
+ }
+
+ const auto button = GRenderWindow::QtButtonToMouseButton(event->button());
+ input_subsystem->GetMouse()->PressButton(0, 0, 0, 0, button);
+}
+
+void ConfigureRingController::keyPressEvent(QKeyEvent* event) {
+ if (!input_setter || !event) {
+ return;
+ }
+ event->ignore();
+ if (event->key() != Qt::Key_Escape) {
+ input_subsystem->GetKeyboard()->PressKey(event->key());
+ }
+}
+
+QString ConfigureRingController::ButtonToText(const Common::ParamPackage& param) {
+ if (!param.Has("engine")) {
+ return QObject::tr("[not set]");
+ }
+
+ const QString toggle = QString::fromStdString(param.Get("toggle", false) ? "~" : "");
+ const QString inverted = QString::fromStdString(param.Get("inverted", false) ? "!" : "");
+ const auto common_button_name = input_subsystem->GetButtonName(param);
+
+ // Retrieve the names from Qt
+ if (param.Get("engine", "") == "keyboard") {
+ const QString button_str = GetKeyName(param.Get("code", 0));
+ return QObject::tr("%1%2").arg(toggle, button_str);
+ }
+
+ if (common_button_name == Common::Input::ButtonNames::Invalid) {
+ return QObject::tr("[invalid]");
+ }
+
+ if (common_button_name == Common::Input::ButtonNames::Engine) {
+ return QString::fromStdString(param.Get("engine", ""));
+ }
+
+ if (common_button_name == Common::Input::ButtonNames::Value) {
+ if (param.Has("hat")) {
+ const QString hat = QString::fromStdString(param.Get("direction", ""));
+ return QObject::tr("%1%2Hat %3").arg(toggle, inverted, hat);
+ }
+ if (param.Has("axis")) {
+ const QString axis = QString::fromStdString(param.Get("axis", ""));
+ return QObject::tr("%1%2Axis %3").arg(toggle, inverted, axis);
+ }
+ if (param.Has("axis_x") && param.Has("axis_y") && param.Has("axis_z")) {
+ const QString axis_x = QString::fromStdString(param.Get("axis_x", ""));
+ const QString axis_y = QString::fromStdString(param.Get("axis_y", ""));
+ const QString axis_z = QString::fromStdString(param.Get("axis_z", ""));
+ return QObject::tr("%1%2Axis %3,%4,%5").arg(toggle, inverted, axis_x, axis_y, axis_z);
+ }
+ if (param.Has("motion")) {
+ const QString motion = QString::fromStdString(param.Get("motion", ""));
+ return QObject::tr("%1%2Motion %3").arg(toggle, inverted, motion);
+ }
+ if (param.Has("button")) {
+ const QString button = QString::fromStdString(param.Get("button", ""));
+ return QObject::tr("%1%2Button %3").arg(toggle, inverted, button);
+ }
+ }
+
+ QString button_name = GetButtonName(common_button_name);
+ if (param.Has("hat")) {
+ return QObject::tr("%1%2Hat %3").arg(toggle, inverted, button_name);
+ }
+ if (param.Has("axis")) {
+ return QObject::tr("%1%2Axis %3").arg(toggle, inverted, button_name);
+ }
+ if (param.Has("motion")) {
+ return QObject::tr("%1%2Axis %3").arg(toggle, inverted, button_name);
+ }
+ if (param.Has("button")) {
+ return QObject::tr("%1%2Button %3").arg(toggle, inverted, button_name);
+ }
+
+ return QObject::tr("[unknown]");
+}
+
+QString ConfigureRingController::AnalogToText(const Common::ParamPackage& param,
+ const std::string& dir) {
+ if (!param.Has("engine")) {
+ return QObject::tr("[not set]");
+ }
+
+ if (param.Get("engine", "") == "analog_from_button") {
+ return ButtonToText(Common::ParamPackage{param.Get(dir, "")});
+ }
+
+ if (!param.Has("axis_x") || !param.Has("axis_y")) {
+ return QObject::tr("[unknown]");
+ }
+
+ const auto engine_str = param.Get("engine", "");
+ const QString axis_x_str = QString::fromStdString(param.Get("axis_x", ""));
+ const QString axis_y_str = QString::fromStdString(param.Get("axis_y", ""));
+ const bool invert_x = param.Get("invert_x", "+") == "-";
+ const bool invert_y = param.Get("invert_y", "+") == "-";
+
+ if (dir == "modifier") {
+ return QObject::tr("[unused]");
+ }
+
+ if (dir == "left") {
+ const QString invert_x_str = QString::fromStdString(invert_x ? "+" : "-");
+ return QObject::tr("Axis %1%2").arg(axis_x_str, invert_x_str);
+ }
+ if (dir == "right") {
+ const QString invert_x_str = QString::fromStdString(invert_x ? "-" : "+");
+ return QObject::tr("Axis %1%2").arg(axis_x_str, invert_x_str);
+ }
+ if (dir == "up") {
+ const QString invert_y_str = QString::fromStdString(invert_y ? "-" : "+");
+ return QObject::tr("Axis %1%2").arg(axis_y_str, invert_y_str);
+ }
+ if (dir == "down") {
+ const QString invert_y_str = QString::fromStdString(invert_y ? "+" : "-");
+ return QObject::tr("Axis %1%2").arg(axis_y_str, invert_y_str);
+ }
+
+ return QObject::tr("[unknown]");
+} \ No newline at end of file
diff --git a/src/yuzu/configuration/configure_ringcon.h b/src/yuzu/configuration/configure_ringcon.h
new file mode 100644
index 000000000..cf9e54f09
--- /dev/null
+++ b/src/yuzu/configuration/configure_ringcon.h
@@ -0,0 +1,85 @@
+// Copyright 2022 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <functional>
+#include <memory>
+#include <QDialog>
+
+namespace InputCommon {
+class InputSubsystem;
+} // namespace InputCommon
+
+namespace Core::HID {
+class HIDCore;
+class EmulatedDevices;
+} // namespace Core::HID
+
+namespace Ui {
+class ConfigureRingController;
+} // namespace Ui
+
+class ConfigureRingController : public QDialog {
+ Q_OBJECT
+
+public:
+ explicit ConfigureRingController(QWidget* parent, InputCommon::InputSubsystem* input_subsystem_,
+ Core::HID::HIDCore& hid_core_);
+ ~ConfigureRingController() override;
+
+ void ApplyConfiguration();
+
+private:
+ void changeEvent(QEvent* event) override;
+ void RetranslateUI();
+
+ void UpdateUI();
+
+ /// Load configuration settings.
+ void LoadConfiguration();
+
+ /// Restore all buttons to their default values.
+ void RestoreDefaults();
+
+ /// Called when the button was pressed.
+ void HandleClick(QPushButton* button,
+ std::function<void(const Common::ParamPackage&)> new_input_setter,
+ InputCommon::Polling::InputType type);
+
+ /// Finish polling and configure input using the input_setter.
+ void SetPollingResult(const Common::ParamPackage& params, bool abort);
+
+ /// Checks whether a given input can be accepted.
+ bool IsInputAcceptable(const Common::ParamPackage& params) const;
+
+ /// Handle mouse button press events.
+ void mousePressEvent(QMouseEvent* event) override;
+
+ /// Handle key press events.
+ void keyPressEvent(QKeyEvent* event) override;
+
+ QString ButtonToText(const Common::ParamPackage& param);
+
+ QString AnalogToText(const Common::ParamPackage& param, const std::string& dir);
+
+ static constexpr int ANALOG_SUB_BUTTONS_NUM = 2;
+
+ // A group of four QPushButtons represent one analog input. The buttons each represent left,
+ // right, respectively.
+ std::array<QPushButton*, ANALOG_SUB_BUTTONS_NUM> analog_map_buttons;
+
+ static const std::array<std::string, ANALOG_SUB_BUTTONS_NUM> analog_sub_buttons;
+
+ std::unique_ptr<QTimer> timeout_timer;
+ std::unique_ptr<QTimer> poll_timer;
+
+ /// This will be the the setting function when an input is awaiting configuration.
+ std::optional<std::function<void(const Common::ParamPackage&)>> input_setter;
+
+ InputCommon::InputSubsystem* input_subsystem;
+ Core::HID::EmulatedDevices* emulated_device;
+
+ std::unique_ptr<Ui::ConfigureRingController> ui;
+};
diff --git a/src/yuzu/configuration/configure_ringcon.ui b/src/yuzu/configuration/configure_ringcon.ui
new file mode 100644
index 000000000..9ec634dd4
--- /dev/null
+++ b/src/yuzu/configuration/configure_ringcon.ui
@@ -0,0 +1,278 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ConfigureRingController</class>
+ <widget class="QDialog" name="ConfigureRingController">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>298</width>
+ <height>339</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Configure Ring Controller</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QLabel" name="label_2">
+ <property name="minimumSize">
+ <size>
+ <width>280</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>If you want to use this controller configure player 1 as right controller and player 2 as dual joycon before starting the game to allow this controller to be detected properly.</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>10</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="RingAnalog">
+ <property name="title">
+ <string>Ring Sensor Parameters</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="sizeConstraint">
+ <enum>QLayout::SetDefaultConstraint</enum>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>6</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="buttonRingAnalogPullHorizontaLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="buttonRingAnalogPullGroup">
+ <property name="title">
+ <string>Pull</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonRingAnalogPullVerticalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="buttonRingAnalogPull">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>Pull</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="buttonRingAnalogPushGroup">
+ <property name="title">
+ <string>Push</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonRingAnalogPushVerticalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="buttonRingAnalogPush">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>Push</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" name="sliderRingAnalogDeadzoneVerticalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="sizeConstraint">
+ <enum>QLayout::SetDefaultConstraint</enum>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>10</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="sliderRingAnalogDeadzoneHorizontalLayout">
+ <item>
+ <widget class="QLabel" name="labelRingAnalogDeadzone">
+ <property name="text">
+ <string>Deadzone: 0%</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignHCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QSlider" name="sliderRingAnalogDeadzone">
+ <property name="maximum">
+ <number>100</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QPushButton" name="restore_defaults_button">
+ <property name="text">
+ <string>Restore Defaults</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>ConfigureRingController</receiver>
+ <slot>accept()</slot>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>ConfigureRingController</receiver>
+ <slot>reject()</slot>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h
index 211a84dde..f2a986ed8 100644
--- a/src/yuzu/game_list_p.h
+++ b/src/yuzu/game_list_p.h
@@ -164,8 +164,8 @@ public:
}
const CompatStatus& status = iterator->second;
setData(compatibility, CompatNumberRole);
- setText(QObject::tr(status.text));
- setToolTip(QObject::tr(status.tooltip));
+ setText(tr(status.text));
+ setToolTip(tr(status.tooltip));
setData(CreateCirclePixmapFromColor(status.color), Qt::DecorationRole);
}
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index ecd2f9f24..5e26aad29 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -24,7 +24,7 @@
#include "core/file_sys/vfs_real.h"
#include "core/frontend/applets/controller.h"
#include "core/frontend/applets/general_frontend.h"
-#include "core/frontend/applets/mii.h"
+#include "core/frontend/applets/mii_edit.h"
#include "core/frontend/applets/software_keyboard.h"
#include "core/hid/emulated_controller.h"
#include "core/hid/hid_core.h"
@@ -152,7 +152,8 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
}
#endif
-constexpr int default_mouse_timeout = 2500;
+constexpr int default_mouse_hide_timeout = 2500;
+constexpr int default_mouse_center_timeout = 10;
/**
* "Callouts" are one-time instructional messages shown to the user. In the config settings, there
@@ -287,13 +288,14 @@ GMainWindow::GMainWindow()
ui->menubar->setCursor(QCursor());
statusBar()->setCursor(QCursor());
- mouse_hide_timer.setInterval(default_mouse_timeout);
+ mouse_hide_timer.setInterval(default_mouse_hide_timeout);
connect(&mouse_hide_timer, &QTimer::timeout, this, &GMainWindow::HideMouseCursor);
connect(ui->menubar, &QMenuBar::hovered, this, &GMainWindow::ShowMouseCursor);
- MigrateConfigFiles();
+ mouse_center_timer.setInterval(default_mouse_center_timeout);
+ connect(&mouse_center_timer, &QTimer::timeout, this, &GMainWindow::CenterMouseCursor);
- ui->action_Fullscreen->setChecked(false);
+ MigrateConfigFiles();
#if defined(HAVE_SDL2) && !defined(_WIN32)
SDL_InitSubSystem(SDL_INIT_VIDEO);
@@ -312,17 +314,20 @@ GMainWindow::GMainWindow()
}
QString game_path;
+ bool has_gamepath = false;
+ bool is_fullscreen = false;
for (int i = 1; i < args.size(); ++i) {
// Preserves drag/drop functionality
if (args.size() == 2 && !args[1].startsWith(QChar::fromLatin1('-'))) {
game_path = args[1];
+ has_gamepath = true;
break;
}
// Launch game in fullscreen mode
if (args[i] == QStringLiteral("-f")) {
- ui->action_Fullscreen->setChecked(true);
+ is_fullscreen = true;
continue;
}
@@ -365,9 +370,15 @@ GMainWindow::GMainWindow()
}
game_path = args[++i];
+ has_gamepath = true;
}
}
+ // Override fullscreen setting if gamepath or argument is provided
+ if (has_gamepath || is_fullscreen) {
+ ui->action_Fullscreen->setChecked(is_fullscreen);
+ }
+
if (!game_path.isEmpty()) {
BootGame(game_path);
}
@@ -582,7 +593,7 @@ void GMainWindow::WebBrowserOpenWebPage(const std::string& main_url,
#ifdef YUZU_USE_QT_WEB_ENGINE
// Raw input breaks with the web applet, Disable web applets if enabled
- if (disable_web_applet || Settings::values.enable_raw_input) {
+ if (UISettings::values.disable_web_applet || Settings::values.enable_raw_input) {
emit WebBrowserClosed(Service::AM::Applets::WebExitReason::WindowClosed,
"http://localhost/");
return;
@@ -647,12 +658,12 @@ void GMainWindow::WebBrowserOpenWebPage(const std::string& main_url,
connect(exit_action, &QAction::triggered, this, [this, &web_browser_view] {
const auto result = QMessageBox::warning(
this, tr("Disable Web Applet"),
- tr("Disabling the web applet will cause it to not be shown again for the rest of the "
- "emulated session. This can lead to undefined behavior and should only be used with "
- "Super Mario 3D All-Stars. Are you sure you want to disable the web applet?"),
+ tr("Disabling the web applet can lead to undefined behavior and should only be used "
+ "with Super Mario 3D All-Stars. Are you sure you want to disable the web "
+ "applet?\n(This can be re-enabled in the Debug settings.)"),
QMessageBox::Yes | QMessageBox::No);
if (result == QMessageBox::Yes) {
- disable_web_applet = true;
+ UISettings::values.disable_web_applet = true;
web_browser_view.SetFinished(true);
}
});
@@ -1280,8 +1291,8 @@ bool GMainWindow::LoadROM(const QString& filename, u64 program_id, std::size_t p
system->SetAppletFrontendSet({
std::make_unique<QtControllerSelector>(*this), // Controller Selector
std::make_unique<QtErrorDisplay>(*this), // Error Display
+ nullptr, // Mii Editor
nullptr, // Parental Controls
- nullptr, // Mii editor
nullptr, // Photo Viewer
std::make_unique<QtProfileSelector>(*this), // Profile Selector
std::make_unique<QtSoftwareKeyboard>(*this), // Software Keyboard
@@ -3294,10 +3305,26 @@ void GMainWindow::ShowMouseCursor() {
}
}
+void GMainWindow::CenterMouseCursor() {
+ if (emu_thread == nullptr || !Settings::values.mouse_panning) {
+ mouse_center_timer.stop();
+ return;
+ }
+ if (!this->isActiveWindow()) {
+ mouse_center_timer.stop();
+ return;
+ }
+ const int center_x = render_window->width() / 2;
+ const int center_y = render_window->height() / 2;
+
+ QCursor::setPos(mapToGlobal({center_x, center_y}));
+}
+
void GMainWindow::OnMouseActivity() {
if (!Settings::values.mouse_panning) {
ShowMouseCursor();
}
+ mouse_center_timer.stop();
}
void GMainWindow::OnCoreError(Core::SystemResultStatus result, std::string details) {
@@ -3570,6 +3597,22 @@ void GMainWindow::dragMoveEvent(QDragMoveEvent* event) {
AcceptDropEvent(event);
}
+void GMainWindow::leaveEvent(QEvent* event) {
+ if (Settings::values.mouse_panning) {
+ const QRect& rect = geometry();
+ QPoint position = QCursor::pos();
+
+ qint32 x = qBound(rect.left(), position.x(), rect.right());
+ qint32 y = qBound(rect.top(), position.y(), rect.bottom());
+ // Only start the timer if the mouse has left the window bound.
+ // The leave event is also triggered when the window looses focus.
+ if (x != position.x() || y != position.y()) {
+ mouse_center_timer.start();
+ }
+ event->accept();
+ }
+}
+
bool GMainWindow::ConfirmChangeGame() {
if (emu_thread == nullptr)
return true;
@@ -3645,6 +3688,14 @@ void GMainWindow::UpdateUITheme() {
setStyleSheet({});
}
+ QPalette new_pal(qApp->palette());
+ if (UISettings::IsDarkTheme()) {
+ new_pal.setColor(QPalette::Link, QColor(0, 190, 255, 255));
+ } else {
+ new_pal.setColor(QPalette::Link, QColor(0, 140, 200, 255));
+ }
+ qApp->setPalette(new_pal);
+
QIcon::setThemeName(current_theme);
QIcon::setThemeSearchPaths(theme_paths);
}
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 7659c8000..b399e9b01 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -328,6 +328,7 @@ private:
void UpdateUISettings();
void HideMouseCursor();
void ShowMouseCursor();
+ void CenterMouseCursor();
void OpenURL(const QUrl& url);
void LoadTranslation();
void OpenPerGameConfiguration(u64 title_id, const std::string& file_name);
@@ -372,6 +373,7 @@ private:
bool auto_paused = false;
bool auto_muted = false;
QTimer mouse_hide_timer;
+ QTimer mouse_center_timer;
// FS
std::shared_ptr<FileSys::VfsFilesystem> vfs;
@@ -398,9 +400,6 @@ private:
// Last game booted, used for multi-process apps
QString last_filename_booted;
- // Disables the web applet for the rest of the emulated session
- bool disable_web_applet{};
-
// Applets
QtSoftwareKeyboardDialog* software_keyboard = nullptr;
@@ -421,4 +420,5 @@ protected:
void dropEvent(QDropEvent* event) override;
void dragEnterEvent(QDragEnterEvent* event) override;
void dragMoveEvent(QDragMoveEvent* event) override;
+ void leaveEvent(QEvent* event) override;
};
diff --git a/src/yuzu/uisettings.cpp b/src/yuzu/uisettings.cpp
index 21683576c..f683b80f7 100644
--- a/src/yuzu/uisettings.cpp
+++ b/src/yuzu/uisettings.cpp
@@ -15,6 +15,14 @@ const Themes themes{{
{"Midnight Blue Colorful", "colorful_midnight_blue"},
}};
+bool IsDarkTheme() {
+ const auto& theme = UISettings::values.theme;
+ return theme == QStringLiteral("qdarkstyle") ||
+ theme == QStringLiteral("qdarkstyle_midnight_blue") ||
+ theme == QStringLiteral("colorful_dark") ||
+ theme == QStringLiteral("colorful_midnight_blue");
+}
+
Values values = {};
} // namespace UISettings
diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h
index 06e8b46da..15ba9ea17 100644
--- a/src/yuzu/uisettings.h
+++ b/src/yuzu/uisettings.h
@@ -17,6 +17,8 @@
namespace UISettings {
+bool IsDarkTheme();
+
struct ContextualShortcut {
QString keyseq;
QString controller_keyseq;
@@ -114,6 +116,7 @@ struct Values {
bool configuration_applied;
bool reset_to_defaults;
+ Settings::BasicSetting<bool> disable_web_applet{true, "disable_web_applet"};
};
extern Values values;
diff --git a/src/yuzu/util/controller_navigation.cpp b/src/yuzu/util/controller_navigation.cpp
index c2b13123d..9a1868cae 100644
--- a/src/yuzu/util/controller_navigation.cpp
+++ b/src/yuzu/util/controller_navigation.cpp
@@ -39,7 +39,7 @@ void ControllerNavigation::TriggerButton(Settings::NativeButton::Values native_b
}
void ControllerNavigation::ControllerUpdateEvent(Core::HID::ControllerTriggerType type) {
- std::lock_guard lock{mutex};
+ std::scoped_lock lock{mutex};
if (!Settings::values.controller_navigation) {
return;
}
diff --git a/src/yuzu_cmd/CMakeLists.txt b/src/yuzu_cmd/CMakeLists.txt
index 74fc24972..c8901f2df 100644
--- a/src/yuzu_cmd/CMakeLists.txt
+++ b/src/yuzu_cmd/CMakeLists.txt
@@ -45,7 +45,7 @@ if (YUZU_USE_EXTERNAL_SDL2)
endif()
if(UNIX AND NOT APPLE)
- install(TARGETS yuzu-cmd RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin")
+ install(TARGETS yuzu-cmd)
endif()
if (MSVC)
diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h
index 34782c378..f34d6b728 100644
--- a/src/yuzu_cmd/default_ini.h
+++ b/src/yuzu_cmd/default_ini.h
@@ -342,12 +342,6 @@ fps_cap =
# null: No audio output
output_engine =
-# Whether or not to enable the audio-stretching post-processing effect.
-# This effect adjusts audio speed to match emulation speed and helps prevent audio stutter,
-# at the cost of increasing audio latency.
-# 0: No, 1 (default): Yes
-enable_audio_stretching =
-
# Which audio device to use.
# auto (default): Auto-select
output_device =
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
index 57f807826..ae2e62dc5 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
@@ -123,14 +123,15 @@ void EmuWindow_SDL2::ShowCursor(bool show_cursor) {
}
void EmuWindow_SDL2::Fullscreen() {
+ SDL_DisplayMode display_mode;
switch (Settings::values.fullscreen_mode.GetValue()) {
case Settings::FullscreenMode::Exclusive:
- // Set window size to render size before entering fullscreen -- SDL does not resize to
- // display dimensions in this mode.
- // TODO: Multiply the window size by resolution_factor (for both docked modes)
- if (Settings::values.use_docked_mode) {
- SDL_SetWindowSize(render_window, Layout::ScreenDocked::Width,
- Layout::ScreenDocked::Height);
+ // Set window size to render size before entering fullscreen -- SDL2 does not resize window
+ // to display dimensions automatically in this mode.
+ if (SDL_GetDesktopDisplayMode(0, &display_mode) == 0) {
+ SDL_SetWindowSize(render_window, display_mode.w, display_mode.h);
+ } else {
+ LOG_ERROR(Frontend, "SDL_GetDesktopDisplayMode failed: {}", SDL_GetError());
}
if (SDL_SetWindowFullscreen(render_window, SDL_WINDOW_FULLSCREEN) == 0) {
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp
index 14bf82f39..ab12dd15d 100644
--- a/src/yuzu_cmd/yuzu.cpp
+++ b/src/yuzu_cmd/yuzu.cpp
@@ -74,6 +74,7 @@ static void PrintVersion() {
int main(int argc, char** argv) {
Common::Log::Initialize();
Common::Log::SetColorConsoleBackendEnabled(true);
+ Common::Log::Start();
Common::DetachedTasks detached_tasks;
int option_index = 0;